Source code

Revision control

Other Tools

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/. */
5
6
#include <algorithm>
7
8
#include "Predictor.h"
9
10
#include "nsAppDirectoryServiceDefs.h"
11
#include "nsICacheStorage.h"
12
#include "nsICacheStorageService.h"
13
#include "nsICachingChannel.h"
14
#include "nsICancelable.h"
15
#include "nsIChannel.h"
16
#include "nsContentUtils.h"
17
#include "nsIDNSService.h"
18
#include "mozilla/dom/Document.h"
19
#include "nsIFile.h"
20
#include "nsIHttpChannel.h"
21
#include "nsIInputStream.h"
22
#include "nsIIOService.h"
23
#include "nsILoadContext.h"
24
#include "nsILoadContextInfo.h"
25
#include "nsILoadGroup.h"
26
#include "nsINetworkPredictorVerifier.h"
27
#include "nsIObserverService.h"
28
#include "nsIPrefBranch.h"
29
#include "nsIPrefService.h"
30
#include "nsISpeculativeConnect.h"
31
#include "nsITimer.h"
32
#include "nsIURI.h"
33
#include "nsNetUtil.h"
34
#include "nsServiceManagerUtils.h"
35
#include "nsStreamUtils.h"
36
#include "nsString.h"
37
#include "nsThreadUtils.h"
38
#include "mozilla/Logging.h"
39
40
#include "mozilla/Preferences.h"
41
#include "mozilla/StaticPrefs_network.h"
42
#include "mozilla/Telemetry.h"
43
44
#include "mozilla/net/NeckoCommon.h"
45
#include "mozilla/net/NeckoParent.h"
46
47
#include "LoadContextInfo.h"
48
#include "mozilla/ipc/URIUtils.h"
49
#include "SerializedLoadContext.h"
50
#include "mozilla/net/NeckoChild.h"
51
52
#include "mozilla/dom/ContentParent.h"
53
#include "mozilla/ClearOnShutdown.h"
54
55
#include "CacheControlParser.h"
56
#include "ReferrerInfo.h"
57
58
using namespace mozilla;
59
60
namespace mozilla {
61
namespace net {
62
63
Predictor* Predictor::sSelf = nullptr;
64
65
static LazyLogModule gPredictorLog("NetworkPredictor");
66
67
#define PREDICTOR_LOG(args) \
68
MOZ_LOG(gPredictorLog, mozilla::LogLevel::Debug, args)
69
70
#define RETURN_IF_FAILED(_rv) \
71
do { \
72
if (NS_FAILED(_rv)) { \
73
return; \
74
} \
75
} while (0)
76
77
#define NOW_IN_SECONDS() static_cast<uint32_t>(PR_Now() / PR_USEC_PER_SEC)
78
79
static const char PREDICTOR_CLEANED_UP_PREF[] = "network.predictor.cleaned-up";
80
81
// All these time values are in sec
82
static const uint32_t ONE_DAY = 86400U;
83
static const uint32_t ONE_WEEK = 7U * ONE_DAY;
84
static const uint32_t ONE_MONTH = 30U * ONE_DAY;
85
static const uint32_t ONE_YEAR = 365U * ONE_DAY;
86
87
static const uint32_t STARTUP_WINDOW = 5U * 60U; // 5min
88
89
// Version of metadata entries we expect
90
static const uint32_t METADATA_VERSION = 1;
91
92
// Flags available in entries
93
// FLAG_PREFETCHABLE - we have determined that this item is eligible for
94
// prefetch
95
static const uint32_t FLAG_PREFETCHABLE = 1 << 0;
96
97
// We save 12 bits in the "flags" section of our metadata for actual flags, the
98
// rest are to keep track of a rolling count of which loads a resource has been
99
// used on to determine if we can prefetch that resource or not;
100
static const uint8_t kRollingLoadOffset = 12;
101
static const int32_t kMaxPrefetchRollingLoadCount = 20;
102
static const uint32_t kFlagsMask = ((1 << kRollingLoadOffset) - 1);
103
104
static bool sEsniEnabled = false;
105
106
// ID Extensions for cache entries
107
#define PREDICTOR_ORIGIN_EXTENSION "predictor-origin"
108
109
// Get the full origin (scheme, host, port) out of a URI (maybe should be part
110
// of nsIURI instead?)
111
static nsresult ExtractOrigin(nsIURI* uri, nsIURI** originUri) {
112
nsAutoCString s;
113
nsresult rv = nsContentUtils::GetASCIIOrigin(uri, s);
114
NS_ENSURE_SUCCESS(rv, rv);
115
116
return NS_NewURI(originUri, s);
117
}
118
119
// All URIs we get passed *must* be http or https if they're not null. This
120
// helps ensure that.
121
static bool IsNullOrHttp(nsIURI* uri) {
122
if (!uri) {
123
return true;
124
}
125
126
return uri->SchemeIs("http") || uri->SchemeIs("https");
127
}
128
129
// Listener for the speculative DNS requests we'll fire off, which just ignores
130
// the result (since we're just trying to warm the cache). This also exists to
131
// reduce round-trips to the main thread, by being something threadsafe the
132
// Predictor can use.
133
134
NS_IMPL_ISUPPORTS(Predictor::DNSListener, nsIDNSListener);
135
136
NS_IMETHODIMP
137
Predictor::DNSListener::OnLookupComplete(nsICancelable* request,
138
nsIDNSRecord* rec, nsresult status) {
139
return NS_OK;
140
}
141
142
NS_IMETHODIMP
143
Predictor::DNSListener::OnLookupByTypeComplete(nsICancelable* request,
144
nsIDNSByTypeRecord* res,
145
nsresult status) {
146
return NS_OK;
147
}
148
149
// Class to proxy important information from the initial predictor call through
150
// the cache API and back into the internals of the predictor. We can't use the
151
// predictor itself, as it may have multiple actions in-flight, and each action
152
// has different parameters.
153
NS_IMPL_ISUPPORTS(Predictor::Action, nsICacheEntryOpenCallback);
154
155
Predictor::Action::Action(bool fullUri, bool predict, Predictor::Reason reason,
156
nsIURI* targetURI, nsIURI* sourceURI,
157
nsINetworkPredictorVerifier* verifier,
158
Predictor* predictor)
159
: mFullUri(fullUri),
160
mPredict(predict),
161
mTargetURI(targetURI),
162
mSourceURI(sourceURI),
163
mVerifier(verifier),
164
mStackCount(0),
165
mPredictor(predictor) {
166
mStartTime = TimeStamp::Now();
167
if (mPredict) {
168
mPredictReason = reason.mPredict;
169
} else {
170
mLearnReason = reason.mLearn;
171
}
172
}
173
174
Predictor::Action::Action(bool fullUri, bool predict, Predictor::Reason reason,
175
nsIURI* targetURI, nsIURI* sourceURI,
176
nsINetworkPredictorVerifier* verifier,
177
Predictor* predictor, uint8_t stackCount)
178
: mFullUri(fullUri),
179
mPredict(predict),
180
mTargetURI(targetURI),
181
mSourceURI(sourceURI),
182
mVerifier(verifier),
183
mStackCount(stackCount),
184
mPredictor(predictor) {
185
mStartTime = TimeStamp::Now();
186
if (mPredict) {
187
mPredictReason = reason.mPredict;
188
} else {
189
mLearnReason = reason.mLearn;
190
}
191
}
192
193
NS_IMETHODIMP
194
Predictor::Action::OnCacheEntryCheck(nsICacheEntry* entry,
195
nsIApplicationCache* appCache,
196
uint32_t* result) {
197
*result = nsICacheEntryOpenCallback::ENTRY_WANTED;
198
return NS_OK;
199
}
200
201
NS_IMETHODIMP
202
Predictor::Action::OnCacheEntryAvailable(nsICacheEntry* entry, bool isNew,
203
nsIApplicationCache* appCache,
204
nsresult result) {
205
MOZ_ASSERT(NS_IsMainThread(), "Got cache entry off main thread!");
206
207
nsAutoCString targetURI, sourceURI;
208
mTargetURI->GetAsciiSpec(targetURI);
209
if (mSourceURI) {
210
mSourceURI->GetAsciiSpec(sourceURI);
211
}
212
PREDICTOR_LOG(
213
("OnCacheEntryAvailable %p called. entry=%p mFullUri=%d mPredict=%d "
214
"mPredictReason=%d mLearnReason=%d mTargetURI=%s "
215
"mSourceURI=%s mStackCount=%d isNew=%d result=0x%08" PRIx32,
216
this, entry, mFullUri, mPredict, mPredictReason, mLearnReason,
217
targetURI.get(), sourceURI.get(), mStackCount, isNew,
218
static_cast<uint32_t>(result)));
219
if (NS_FAILED(result)) {
220
PREDICTOR_LOG(
221
("OnCacheEntryAvailable %p FAILED to get cache entry (0x%08" PRIX32
222
"). Aborting.",
223
this, static_cast<uint32_t>(result)));
224
return NS_OK;
225
}
226
Telemetry::AccumulateTimeDelta(Telemetry::PREDICTOR_WAIT_TIME, mStartTime);
227
if (mPredict) {
228
bool predicted =
229
mPredictor->PredictInternal(mPredictReason, entry, isNew, mFullUri,
230
mTargetURI, mVerifier, mStackCount);
231
Telemetry::AccumulateTimeDelta(Telemetry::PREDICTOR_PREDICT_WORK_TIME,
232
mStartTime);
233
if (predicted) {
234
Telemetry::AccumulateTimeDelta(
235
Telemetry::PREDICTOR_PREDICT_TIME_TO_ACTION, mStartTime);
236
} else {
237
Telemetry::AccumulateTimeDelta(
238
Telemetry::PREDICTOR_PREDICT_TIME_TO_INACTION, mStartTime);
239
}
240
} else {
241
mPredictor->LearnInternal(mLearnReason, entry, isNew, mFullUri, mTargetURI,
242
mSourceURI);
243
Telemetry::AccumulateTimeDelta(Telemetry::PREDICTOR_LEARN_WORK_TIME,
244
mStartTime);
245
}
246
247
return NS_OK;
248
}
249
250
NS_IMPL_ISUPPORTS(Predictor, nsINetworkPredictor, nsIObserver,
251
nsISpeculativeConnectionOverrider, nsIInterfaceRequestor,
252
nsICacheEntryMetaDataVisitor, nsINetworkPredictorVerifier)
253
254
Predictor::Predictor()
255
: mInitialized(false),
256
mCleanedUp(false),
257
mStartupTime(0),
258
mLastStartupTime(0),
259
mStartupCount(1) {
260
MOZ_ASSERT(!sSelf, "multiple Predictor instances!");
261
sSelf = this;
262
}
263
264
Predictor::~Predictor() {
265
if (mInitialized) Shutdown();
266
267
sSelf = nullptr;
268
}
269
270
// Predictor::nsIObserver
271
272
nsresult Predictor::InstallObserver() {
273
MOZ_ASSERT(NS_IsMainThread(), "Installing observer off main thread");
274
275
nsresult rv = NS_OK;
276
nsCOMPtr<nsIObserverService> obs = mozilla::services::GetObserverService();
277
if (!obs) {
278
return NS_ERROR_NOT_AVAILABLE;
279
}
280
281
rv = obs->AddObserver(this, NS_XPCOM_SHUTDOWN_OBSERVER_ID, false);
282
NS_ENSURE_SUCCESS(rv, rv);
283
284
mCleanedUp = Preferences::GetBool(PREDICTOR_CLEANED_UP_PREF, false);
285
286
if (!mCleanedUp) {
287
NS_NewTimerWithObserver(getter_AddRefs(mCleanupTimer), this, 60 * 1000,
288
nsITimer::TYPE_ONE_SHOT);
289
}
290
291
return rv;
292
}
293
294
void Predictor::RemoveObserver() {
295
MOZ_ASSERT(NS_IsMainThread(), "Removing observer off main thread");
296
297
nsCOMPtr<nsIObserverService> obs = mozilla::services::GetObserverService();
298
if (obs) {
299
obs->RemoveObserver(this, NS_XPCOM_SHUTDOWN_OBSERVER_ID);
300
}
301
302
if (mCleanupTimer) {
303
mCleanupTimer->Cancel();
304
mCleanupTimer = nullptr;
305
}
306
}
307
308
NS_IMETHODIMP
309
Predictor::Observe(nsISupports* subject, const char* topic,
310
const char16_t* data_unicode) {
311
nsresult rv = NS_OK;
312
MOZ_ASSERT(NS_IsMainThread(),
313
"Predictor observing something off main thread!");
314
315
if (!strcmp(NS_XPCOM_SHUTDOWN_OBSERVER_ID, topic)) {
316
Shutdown();
317
} else if (!strcmp("timer-callback", topic)) {
318
MaybeCleanupOldDBFiles();
319
mCleanupTimer = nullptr;
320
}
321
322
return rv;
323
}
324
325
// Predictor::nsISpeculativeConnectionOverrider
326
327
NS_IMETHODIMP
328
Predictor::GetIgnoreIdle(bool* ignoreIdle) {
329
*ignoreIdle = true;
330
return NS_OK;
331
}
332
333
NS_IMETHODIMP
334
Predictor::GetParallelSpeculativeConnectLimit(
335
uint32_t* parallelSpeculativeConnectLimit) {
336
*parallelSpeculativeConnectLimit = 6;
337
return NS_OK;
338
}
339
340
NS_IMETHODIMP
341
Predictor::GetIsFromPredictor(bool* isFromPredictor) {
342
*isFromPredictor = true;
343
return NS_OK;
344
}
345
346
NS_IMETHODIMP
347
Predictor::GetAllow1918(bool* allow1918) {
348
*allow1918 = false;
349
return NS_OK;
350
}
351
352
// Predictor::nsIInterfaceRequestor
353
354
NS_IMETHODIMP
355
Predictor::GetInterface(const nsIID& iid, void** result) {
356
return QueryInterface(iid, result);
357
}
358
359
// Predictor::nsICacheEntryMetaDataVisitor
360
361
#define SEEN_META_DATA "predictor::seen"
362
#define RESOURCE_META_DATA "predictor::resource-count"
363
#define META_DATA_PREFIX "predictor::"
364
365
static bool IsURIMetadataElement(const char* key) {
366
return StringBeginsWith(nsDependentCString(key),
367
NS_LITERAL_CSTRING(META_DATA_PREFIX)) &&
368
!NS_LITERAL_CSTRING(SEEN_META_DATA).Equals(key) &&
369
!NS_LITERAL_CSTRING(RESOURCE_META_DATA).Equals(key);
370
}
371
372
nsresult Predictor::OnMetaDataElement(const char* asciiKey,
373
const char* asciiValue) {
374
MOZ_ASSERT(NS_IsMainThread());
375
376
if (!IsURIMetadataElement(asciiKey)) {
377
// This isn't a bit of metadata we care about
378
return NS_OK;
379
}
380
381
nsCString key, value;
382
key.AssignASCII(asciiKey);
383
value.AssignASCII(asciiValue);
384
mKeysToOperateOn.AppendElement(key);
385
mValuesToOperateOn.AppendElement(value);
386
387
return NS_OK;
388
}
389
390
// Predictor::nsINetworkPredictor
391
392
nsresult Predictor::Init() {
393
MOZ_DIAGNOSTIC_ASSERT(!IsNeckoChild());
394
395
if (!NS_IsMainThread()) {
396
MOZ_ASSERT(false, "Predictor::Init called off the main thread!");
397
return NS_ERROR_UNEXPECTED;
398
}
399
400
nsresult rv = NS_OK;
401
402
rv = InstallObserver();
403
NS_ENSURE_SUCCESS(rv, rv);
404
405
mLastStartupTime = mStartupTime = NOW_IN_SECONDS();
406
407
if (!mDNSListener) {
408
mDNSListener = new DNSListener();
409
}
410
411
mCacheStorageService =
412
do_GetService("@mozilla.org/netwerk/cache-storage-service;1", &rv);
413
NS_ENSURE_SUCCESS(rv, rv);
414
415
mSpeculativeService = do_GetService("@mozilla.org/network/io-service;1", &rv);
416
NS_ENSURE_SUCCESS(rv, rv);
417
418
rv = NS_NewURI(getter_AddRefs(mStartupURI), "predictor://startup");
419
NS_ENSURE_SUCCESS(rv, rv);
420
421
mDnsService = do_GetService("@mozilla.org/network/dns-service;1", &rv);
422
NS_ENSURE_SUCCESS(rv, rv);
423
424
Preferences::AddBoolVarCache(&sEsniEnabled, "network.security.esni.enabled");
425
426
mInitialized = true;
427
428
return rv;
429
}
430
431
namespace {
432
class PredictorThreadShutdownRunner : public Runnable {
433
public:
434
PredictorThreadShutdownRunner(nsIThread* ioThread, bool success)
435
: Runnable("net::PredictorThreadShutdownRunner"),
436
mIOThread(ioThread),
437
mSuccess(success) {}
438
~PredictorThreadShutdownRunner() = default;
439
440
NS_IMETHOD Run() override {
441
MOZ_ASSERT(NS_IsMainThread(), "Shutting down io thread off main thread!");
442
if (mSuccess) {
443
// This means the cleanup happened. Mark so we don't try in the
444
// future.
445
Preferences::SetBool(PREDICTOR_CLEANED_UP_PREF, true);
446
}
447
return mIOThread->AsyncShutdown();
448
}
449
450
private:
451
nsCOMPtr<nsIThread> mIOThread;
452
bool mSuccess;
453
};
454
455
class PredictorOldCleanupRunner : public Runnable {
456
public:
457
PredictorOldCleanupRunner(nsIThread* ioThread, nsIFile* dbFile)
458
: Runnable("net::PredictorOldCleanupRunner"),
459
mIOThread(ioThread),
460
mDBFile(dbFile) {}
461
462
~PredictorOldCleanupRunner() = default;
463
464
NS_IMETHOD Run() override {
465
MOZ_ASSERT(!NS_IsMainThread(), "Cleaning up old files on main thread!");
466
nsresult rv = CheckForAndDeleteOldDBFiles();
467
RefPtr<PredictorThreadShutdownRunner> runner =
468
new PredictorThreadShutdownRunner(mIOThread, NS_SUCCEEDED(rv));
469
NS_DispatchToMainThread(runner);
470
return NS_OK;
471
}
472
473
private:
474
nsresult CheckForAndDeleteOldDBFiles() {
475
nsCOMPtr<nsIFile> oldDBFile;
476
nsresult rv = mDBFile->GetParent(getter_AddRefs(oldDBFile));
477
NS_ENSURE_SUCCESS(rv, rv);
478
479
rv = oldDBFile->AppendNative(NS_LITERAL_CSTRING("seer.sqlite"));
480
NS_ENSURE_SUCCESS(rv, rv);
481
482
bool fileExists = false;
483
rv = oldDBFile->Exists(&fileExists);
484
NS_ENSURE_SUCCESS(rv, rv);
485
486
if (fileExists) {
487
rv = oldDBFile->Remove(false);
488
NS_ENSURE_SUCCESS(rv, rv);
489
}
490
491
fileExists = false;
492
rv = mDBFile->Exists(&fileExists);
493
NS_ENSURE_SUCCESS(rv, rv);
494
495
if (fileExists) {
496
rv = mDBFile->Remove(false);
497
}
498
499
return rv;
500
}
501
502
nsCOMPtr<nsIThread> mIOThread;
503
nsCOMPtr<nsIFile> mDBFile;
504
};
505
506
class PredictorLearnRunnable final : public Runnable {
507
public:
508
PredictorLearnRunnable(nsIURI* targetURI, nsIURI* sourceURI,
509
PredictorLearnReason reason,
510
const OriginAttributes& oa)
511
: Runnable("PredictorLearnRunnable"),
512
mTargetURI(targetURI),
513
mSourceURI(sourceURI),
514
mReason(reason),
515
mOA(oa) {}
516
517
~PredictorLearnRunnable() = default;
518
519
NS_IMETHOD Run() override {
520
if (!gNeckoChild) {
521
// This may have gone away between when this runnable was dispatched and
522
// when it actually runs, so let's be safe here, even though we asserted
523
// earlier.
524
PREDICTOR_LOG(("predictor::learn (async) gNeckoChild went away"));
525
return NS_OK;
526
}
527
528
ipc::URIParams serTargetURI;
529
SerializeURI(mTargetURI, serTargetURI);
530
531
Maybe<ipc::URIParams> serSourceURI;
532
SerializeURI(mSourceURI, serSourceURI);
533
534
PREDICTOR_LOG(("predictor::learn (async) forwarding to parent"));
535
gNeckoChild->SendPredLearn(serTargetURI, serSourceURI, mReason, mOA);
536
537
return NS_OK;
538
}
539
540
private:
541
nsCOMPtr<nsIURI> mTargetURI;
542
nsCOMPtr<nsIURI> mSourceURI;
543
PredictorLearnReason mReason;
544
const OriginAttributes mOA;
545
};
546
547
} // namespace
548
549
void Predictor::MaybeCleanupOldDBFiles() {
550
MOZ_ASSERT(NS_IsMainThread());
551
552
if (!StaticPrefs::network_predictor_enabled() || mCleanedUp) {
553
return;
554
}
555
556
mCleanedUp = true;
557
558
// This is used for cleaning up junk left over from the old backend
559
// built on top of sqlite, if necessary.
560
nsCOMPtr<nsIFile> dbFile;
561
nsresult rv = NS_GetSpecialDirectory(NS_APP_USER_PROFILE_50_DIR,
562
getter_AddRefs(dbFile));
563
RETURN_IF_FAILED(rv);
564
rv = dbFile->AppendNative(NS_LITERAL_CSTRING("netpredictions.sqlite"));
565
RETURN_IF_FAILED(rv);
566
567
nsCOMPtr<nsIThread> ioThread;
568
rv = NS_NewNamedThread("NetPredictClean", getter_AddRefs(ioThread));
569
RETURN_IF_FAILED(rv);
570
571
RefPtr<PredictorOldCleanupRunner> runner =
572
new PredictorOldCleanupRunner(ioThread, dbFile);
573
ioThread->Dispatch(runner, NS_DISPATCH_NORMAL);
574
}
575
576
void Predictor::Shutdown() {
577
if (!NS_IsMainThread()) {
578
MOZ_ASSERT(false, "Predictor::Shutdown called off the main thread!");
579
return;
580
}
581
582
RemoveObserver();
583
584
mInitialized = false;
585
}
586
587
nsresult Predictor::Create(nsISupports* aOuter, const nsIID& aIID,
588
void** aResult) {
589
MOZ_ASSERT(NS_IsMainThread());
590
591
nsresult rv;
592
593
if (aOuter != nullptr) {
594
return NS_ERROR_NO_AGGREGATION;
595
}
596
597
RefPtr<Predictor> svc = new Predictor();
598
if (IsNeckoChild()) {
599
NeckoChild::InitNeckoChild();
600
601
// Child threads only need to be call into the public interface methods
602
// so we don't bother with initialization
603
return svc->QueryInterface(aIID, aResult);
604
}
605
606
rv = svc->Init();
607
if (NS_FAILED(rv)) {
608
PREDICTOR_LOG(("Failed to initialize predictor, predictor will be a noop"));
609
}
610
611
// We treat init failure the same as the service being disabled, since this
612
// is all an optimization anyway. No need to freak people out. That's why we
613
// gladly continue on QI'ing here.
614
rv = svc->QueryInterface(aIID, aResult);
615
616
return rv;
617
}
618
619
NS_IMETHODIMP
620
Predictor::Predict(nsIURI* targetURI, nsIURI* sourceURI,
621
PredictorPredictReason reason,
622
JS::HandleValue originAttributes,
623
nsINetworkPredictorVerifier* verifier, JSContext* aCx) {
624
OriginAttributes attrs;
625
626
if (!originAttributes.isObject() || !attrs.Init(aCx, originAttributes)) {
627
return NS_ERROR_INVALID_ARG;
628
}
629
630
return PredictNative(targetURI, sourceURI, reason, attrs, verifier);
631
}
632
633
// Called from the main thread to initiate predictive actions
634
NS_IMETHODIMP
635
Predictor::PredictNative(nsIURI* targetURI, nsIURI* sourceURI,
636
PredictorPredictReason reason,
637
const OriginAttributes& originAttributes,
638
nsINetworkPredictorVerifier* verifier) {
639
MOZ_ASSERT(NS_IsMainThread(),
640
"Predictor interface methods must be called on the main thread");
641
642
PREDICTOR_LOG(("Predictor::Predict"));
643
644
if (IsNeckoChild()) {
645
MOZ_DIAGNOSTIC_ASSERT(gNeckoChild);
646
647
PREDICTOR_LOG((" called on child process"));
648
649
Maybe<ipc::URIParams> serTargetURI, serSourceURI;
650
SerializeURI(targetURI, serTargetURI);
651
SerializeURI(sourceURI, serSourceURI);
652
653
// If two different threads are predicting concurently, this will be
654
// overwritten. Thankfully, we only use this in tests, which will
655
// overwrite mVerifier perhaps multiple times for each individual test;
656
// however, within each test, the multiple predict calls should have the
657
// same verifier.
658
if (verifier) {
659
PREDICTOR_LOG((" was given a verifier"));
660
mChildVerifier = verifier;
661
}
662
PREDICTOR_LOG((" forwarding to parent process"));
663
gNeckoChild->SendPredPredict(serTargetURI, serSourceURI, reason,
664
originAttributes, verifier);
665
return NS_OK;
666
}
667
668
PREDICTOR_LOG((" called on parent process"));
669
670
if (!mInitialized) {
671
PREDICTOR_LOG((" not initialized"));
672
return NS_OK;
673
}
674
675
if (!StaticPrefs::network_predictor_enabled()) {
676
PREDICTOR_LOG((" not enabled"));
677
return NS_OK;
678
}
679
680
if (originAttributes.mPrivateBrowsingId > 0) {
681
// Don't want to do anything in PB mode
682
PREDICTOR_LOG((" in PB mode"));
683
return NS_OK;
684
}
685
686
if (!IsNullOrHttp(targetURI) || !IsNullOrHttp(sourceURI)) {
687
// Nothing we can do for non-HTTP[S] schemes
688
PREDICTOR_LOG((" got non-http[s] URI"));
689
return NS_OK;
690
}
691
692
// Ensure we've been given the appropriate arguments for the kind of
693
// prediction we're being asked to do
694
nsCOMPtr<nsIURI> uriKey = targetURI;
695
nsCOMPtr<nsIURI> originKey;
696
switch (reason) {
697
case nsINetworkPredictor::PREDICT_LINK:
698
if (!targetURI || !sourceURI) {
699
PREDICTOR_LOG((" link invalid URI state"));
700
return NS_ERROR_INVALID_ARG;
701
}
702
// Link hover is a special case where we can predict without hitting the
703
// db, so let's go ahead and fire off that prediction here.
704
PredictForLink(targetURI, sourceURI, originAttributes, verifier);
705
return NS_OK;
706
case nsINetworkPredictor::PREDICT_LOAD:
707
if (!targetURI || sourceURI) {
708
PREDICTOR_LOG((" load invalid URI state"));
709
return NS_ERROR_INVALID_ARG;
710
}
711
break;
712
case nsINetworkPredictor::PREDICT_STARTUP:
713
if (targetURI || sourceURI) {
714
PREDICTOR_LOG((" startup invalid URI state"));
715
return NS_ERROR_INVALID_ARG;
716
}
717
uriKey = mStartupURI;
718
originKey = mStartupURI;
719
break;
720
default:
721
PREDICTOR_LOG((" invalid reason"));
722
return NS_ERROR_INVALID_ARG;
723
}
724
725
Predictor::Reason argReason;
726
argReason.mPredict = reason;
727
728
// First we open the regular cache entry, to ensure we don't gum up the works
729
// waiting on the less-important predictor-only cache entry
730
RefPtr<Predictor::Action> uriAction = new Predictor::Action(
731
Predictor::Action::IS_FULL_URI, Predictor::Action::DO_PREDICT, argReason,
732
targetURI, nullptr, verifier, this);
733
nsAutoCString uriKeyStr;
734
uriKey->GetAsciiSpec(uriKeyStr);
735
PREDICTOR_LOG((" Predict uri=%s reason=%d action=%p", uriKeyStr.get(),
736
reason, uriAction.get()));
737
738
nsCOMPtr<nsICacheStorage> cacheDiskStorage;
739
740
RefPtr<LoadContextInfo> lci = new LoadContextInfo(false, originAttributes);
741
742
nsresult rv = mCacheStorageService->DiskCacheStorage(
743
lci, false, getter_AddRefs(cacheDiskStorage));
744
NS_ENSURE_SUCCESS(rv, rv);
745
746
uint32_t openFlags =
747
nsICacheStorage::OPEN_READONLY | nsICacheStorage::OPEN_SECRETLY |
748
nsICacheStorage::OPEN_PRIORITY | nsICacheStorage::CHECK_MULTITHREADED;
749
cacheDiskStorage->AsyncOpenURI(uriKey, EmptyCString(), openFlags, uriAction);
750
751
// Now we do the origin-only (and therefore predictor-only) entry
752
nsCOMPtr<nsIURI> targetOrigin;
753
rv = ExtractOrigin(uriKey, getter_AddRefs(targetOrigin));
754
NS_ENSURE_SUCCESS(rv, rv);
755
if (!originKey) {
756
originKey = targetOrigin;
757
}
758
759
RefPtr<Predictor::Action> originAction = new Predictor::Action(
760
Predictor::Action::IS_ORIGIN, Predictor::Action::DO_PREDICT, argReason,
761
targetOrigin, nullptr, verifier, this);
762
nsAutoCString originKeyStr;
763
originKey->GetAsciiSpec(originKeyStr);
764
PREDICTOR_LOG((" Predict origin=%s reason=%d action=%p",
765
originKeyStr.get(), reason, originAction.get()));
766
openFlags = nsICacheStorage::OPEN_READONLY | nsICacheStorage::OPEN_SECRETLY |
767
nsICacheStorage::CHECK_MULTITHREADED;
768
cacheDiskStorage->AsyncOpenURI(originKey,
769
NS_LITERAL_CSTRING(PREDICTOR_ORIGIN_EXTENSION),
770
openFlags, originAction);
771
772
PREDICTOR_LOG((" predict returning"));
773
return NS_OK;
774
}
775
776
bool Predictor::PredictInternal(PredictorPredictReason reason,
777
nsICacheEntry* entry, bool isNew, bool fullUri,
778
nsIURI* targetURI,
779
nsINetworkPredictorVerifier* verifier,
780
uint8_t stackCount) {
781
MOZ_ASSERT(NS_IsMainThread());
782
783
PREDICTOR_LOG(("Predictor::PredictInternal"));
784
bool rv = false;
785
786
nsCOMPtr<nsILoadContextInfo> lci;
787
entry->GetLoadContextInfo(getter_AddRefs(lci));
788
789
if (!lci) {
790
return rv;
791
}
792
793
if (reason == nsINetworkPredictor::PREDICT_LOAD) {
794
MaybeLearnForStartup(targetURI, fullUri, *lci->OriginAttributesPtr());
795
}
796
797
if (isNew) {
798
// nothing else we can do here
799
PREDICTOR_LOG((" new entry"));
800
return rv;
801
}
802
803
switch (reason) {
804
case nsINetworkPredictor::PREDICT_LOAD:
805
rv = PredictForPageload(entry, targetURI, stackCount, fullUri, verifier);
806
break;
807
case nsINetworkPredictor::PREDICT_STARTUP:
808
rv = PredictForStartup(entry, fullUri, verifier);
809
break;
810
default:
811
PREDICTOR_LOG((" invalid reason"));
812
MOZ_ASSERT(false, "Got unexpected value for prediction reason");
813
}
814
815
return rv;
816
}
817
818
void Predictor::PredictForLink(nsIURI* targetURI, nsIURI* sourceURI,
819
const OriginAttributes& originAttributes,
820
nsINetworkPredictorVerifier* verifier) {
821
MOZ_ASSERT(NS_IsMainThread());
822
823
PREDICTOR_LOG(("Predictor::PredictForLink"));
824
if (!mSpeculativeService) {
825
PREDICTOR_LOG((" missing speculative service"));
826
return;
827
}
828
829
if (!StaticPrefs::network_predictor_enable_hover_on_ssl()) {
830
if (sourceURI->SchemeIs("https")) {
831
// We don't want to predict from an HTTPS page, to avoid info leakage
832
PREDICTOR_LOG((" Not predicting for link hover - on an SSL page"));
833
return;
834
}
835
}
836
837
nsCOMPtr<nsIPrincipal> principal =
838
BasePrincipal::CreateContentPrincipal(targetURI, originAttributes);
839
840
mSpeculativeService->SpeculativeConnect(targetURI, principal, nullptr);
841
if (verifier) {
842
PREDICTOR_LOG((" sending verification"));
843
verifier->OnPredictPreconnect(targetURI);
844
}
845
}
846
847
// This is the driver for prediction based on a new pageload.
848
static const uint8_t MAX_PAGELOAD_DEPTH = 10;
849
bool Predictor::PredictForPageload(nsICacheEntry* entry, nsIURI* targetURI,
850
uint8_t stackCount, bool fullUri,
851
nsINetworkPredictorVerifier* verifier) {
852
MOZ_ASSERT(NS_IsMainThread());
853
854
PREDICTOR_LOG(("Predictor::PredictForPageload"));
855
856
if (stackCount > MAX_PAGELOAD_DEPTH) {
857
PREDICTOR_LOG((" exceeded recursion depth!"));
858
return false;
859
}
860
861
uint32_t lastLoad;
862
nsresult rv = entry->GetLastFetched(&lastLoad);
863
NS_ENSURE_SUCCESS(rv, false);
864
865
int32_t globalDegradation = CalculateGlobalDegradation(lastLoad);
866
PREDICTOR_LOG((" globalDegradation = %d", globalDegradation));
867
868
int32_t loadCount;
869
rv = entry->GetFetchCount(&loadCount);
870
NS_ENSURE_SUCCESS(rv, false);
871
872
nsCOMPtr<nsILoadContextInfo> lci;
873
874
rv = entry->GetLoadContextInfo(getter_AddRefs(lci));
875
NS_ENSURE_SUCCESS(rv, false);
876
877
nsCOMPtr<nsIURI> redirectURI;
878
if (WouldRedirect(entry, loadCount, lastLoad, globalDegradation,
879
getter_AddRefs(redirectURI))) {
880
mPreconnects.AppendElement(redirectURI);
881
Predictor::Reason reason;
882
reason.mPredict = nsINetworkPredictor::PREDICT_LOAD;
883
RefPtr<Predictor::Action> redirectAction = new Predictor::Action(
884
Predictor::Action::IS_FULL_URI, Predictor::Action::DO_PREDICT, reason,
885
redirectURI, nullptr, verifier, this, stackCount + 1);
886
nsAutoCString redirectUriString;
887
redirectURI->GetAsciiSpec(redirectUriString);
888
889
nsCOMPtr<nsICacheStorage> cacheDiskStorage;
890
891
rv = mCacheStorageService->DiskCacheStorage(
892
lci, false, getter_AddRefs(cacheDiskStorage));
893
NS_ENSURE_SUCCESS(rv, false);
894
895
PREDICTOR_LOG((" Predict redirect uri=%s action=%p",
896
redirectUriString.get(), redirectAction.get()));
897
uint32_t openFlags =
898
nsICacheStorage::OPEN_READONLY | nsICacheStorage::OPEN_SECRETLY |
899
nsICacheStorage::OPEN_PRIORITY | nsICacheStorage::CHECK_MULTITHREADED;
900
cacheDiskStorage->AsyncOpenURI(redirectURI, EmptyCString(), openFlags,
901
redirectAction);
902
return RunPredictions(nullptr, *lci->OriginAttributesPtr(), verifier);
903
}
904
905
CalculatePredictions(entry, targetURI, lastLoad, loadCount, globalDegradation,
906
fullUri);
907
908
return RunPredictions(targetURI, *lci->OriginAttributesPtr(), verifier);
909
}
910
911
// This is the driver for predicting at browser startup time based on pages that
912
// have previously been loaded close to startup.
913
bool Predictor::PredictForStartup(nsICacheEntry* entry, bool fullUri,
914
nsINetworkPredictorVerifier* verifier) {
915
MOZ_ASSERT(NS_IsMainThread());
916
917
PREDICTOR_LOG(("Predictor::PredictForStartup"));
918
919
nsCOMPtr<nsILoadContextInfo> lci;
920
921
nsresult rv = entry->GetLoadContextInfo(getter_AddRefs(lci));
922
NS_ENSURE_SUCCESS(rv, false);
923
924
int32_t globalDegradation = CalculateGlobalDegradation(mLastStartupTime);
925
CalculatePredictions(entry, nullptr, mLastStartupTime, mStartupCount,
926
globalDegradation, fullUri);
927
return RunPredictions(nullptr, *lci->OriginAttributesPtr(), verifier);
928
}
929
930
// This calculates how much to degrade our confidence in our data based on
931
// the last time this top-level resource was loaded. This "global degradation"
932
// applies to *all* subresources we have associated with the top-level
933
// resource. This will be in addition to any reduction in confidence we have
934
// associated with a particular subresource.
935
int32_t Predictor::CalculateGlobalDegradation(uint32_t lastLoad) {
936
MOZ_ASSERT(NS_IsMainThread());
937
938
int32_t globalDegradation;
939
uint32_t delta = NOW_IN_SECONDS() - lastLoad;
940
if (delta < ONE_DAY) {
941
globalDegradation = StaticPrefs::network_predictor_page_degradation_day();
942
} else if (delta < ONE_WEEK) {
943
globalDegradation = StaticPrefs::network_predictor_page_degradation_week();
944
} else if (delta < ONE_MONTH) {
945
globalDegradation = StaticPrefs::network_predictor_page_degradation_month();
946
} else if (delta < ONE_YEAR) {
947
globalDegradation = StaticPrefs::network_predictor_page_degradation_year();
948
} else {
949
globalDegradation = StaticPrefs::network_predictor_page_degradation_max();
950
}
951
952
Telemetry::Accumulate(Telemetry::PREDICTOR_GLOBAL_DEGRADATION,
953
globalDegradation);
954
return globalDegradation;
955
}
956
957
// This calculates our overall confidence that a particular subresource will be
958
// loaded as part of a top-level load.
959
// @param hitCount - the number of times we have loaded this subresource as part
960
// of this top-level load
961
// @param hitsPossible - the number of times we have performed this top-level
962
// load
963
// @param lastHit - the timestamp of the last time we loaded this subresource as
964
// part of this top-level load
965
// @param lastPossible - the timestamp of the last time we performed this
966
// top-level load
967
// @param globalDegradation - the degradation for this top-level load as
968
// determined by CalculateGlobalDegradation
969
int32_t Predictor::CalculateConfidence(uint32_t hitCount, uint32_t hitsPossible,
970
uint32_t lastHit, uint32_t lastPossible,
971
int32_t globalDegradation) {
972
MOZ_ASSERT(NS_IsMainThread());
973
974
Telemetry::AutoCounter<Telemetry::PREDICTOR_PREDICTIONS_CALCULATED>
975
predictionsCalculated;
976
++predictionsCalculated;
977
978
if (!hitsPossible) {
979
return 0;
980
}
981
982
int32_t baseConfidence = (hitCount * 100) / hitsPossible;
983
int32_t maxConfidence = 100;
984
int32_t confidenceDegradation = 0;
985
986
if (lastHit < lastPossible) {
987
// We didn't load this subresource the last time this top-level load was
988
// performed, so let's not bother preconnecting (at the very least).
989
maxConfidence =
990
StaticPrefs::network_predictor_preconnect_min_confidence() - 1;
991
992
// Now calculate how much we want to degrade our confidence based on how
993
// long it's been between the last time we did this top-level load and the
994
// last time this top-level load included this subresource.
995
PRTime delta = lastPossible - lastHit;
996
if (delta == 0) {
997
confidenceDegradation = 0;
998
} else if (delta < ONE_DAY) {
999
confidenceDegradation =
1000
StaticPrefs::network_predictor_subresource_degradation_day();
1001
} else if (delta < ONE_WEEK) {
1002
confidenceDegradation =
1003
StaticPrefs::network_predictor_subresource_degradation_week();
1004
} else if (delta < ONE_MONTH) {
1005
confidenceDegradation =
1006
StaticPrefs::network_predictor_subresource_degradation_month();
1007
} else if (delta < ONE_YEAR) {
1008
confidenceDegradation =
1009
StaticPrefs::network_predictor_subresource_degradation_year();
1010
} else {
1011
confidenceDegradation =
1012
StaticPrefs::network_predictor_subresource_degradation_max();
1013
maxConfidence = 0;
1014
}
1015
}
1016
1017
// Calculate our confidence and clamp it to between 0 and maxConfidence
1018
// (<= 100)
1019
int32_t confidence =
1020
baseConfidence - confidenceDegradation - globalDegradation;
1021
confidence = std::max(confidence, 0);
1022
confidence = std::min(confidence, maxConfidence);
1023
1024
Telemetry::Accumulate(Telemetry::PREDICTOR_BASE_CONFIDENCE, baseConfidence);
1025
Telemetry::Accumulate(Telemetry::PREDICTOR_SUBRESOURCE_DEGRADATION,
1026
confidenceDegradation);
1027
Telemetry::Accumulate(Telemetry::PREDICTOR_CONFIDENCE, confidence);
1028
return confidence;
1029
}
1030
1031
static void MakeMetadataEntry(const uint32_t hitCount, const uint32_t lastHit,
1032
const uint32_t flags, nsCString& newValue) {
1033
newValue.Truncate();
1034
newValue.AppendInt(METADATA_VERSION);
1035
newValue.Append(',');
1036
newValue.AppendInt(hitCount);
1037
newValue.Append(',');
1038
newValue.AppendInt(lastHit);
1039
newValue.Append(',');
1040
newValue.AppendInt(flags);
1041
}
1042
1043
// On every page load, the rolling window gets shifted by one bit, leaving the
1044
// lowest bit at 0, to indicate that the subresource in question has not been
1045
// seen on the most recent page load. If, at some point later during the page
1046
// load, the subresource is seen again, we will then set the lowest bit to 1.
1047
// This is how we keep track of how many of the last n pageloads (for n <= 20) a
1048
// particular subresource has been seen. The rolling window is kept in the upper
1049
// 20 bits of the flags element of the metadata. This saves 12 bits for regular
1050
// old flags.
1051
void Predictor::UpdateRollingLoadCount(nsICacheEntry* entry,
1052
const uint32_t flags, const char* key,
1053
const uint32_t hitCount,
1054
const uint32_t lastHit) {
1055
// Extract just the rolling load count from the flags, shift it to clear the
1056
// lowest bit, and put the new value with the existing flags.
1057
uint32_t rollingLoadCount = flags & ~kFlagsMask;
1058
rollingLoadCount <<= 1;
1059
uint32_t newFlags = (flags & kFlagsMask) | rollingLoadCount;
1060
1061
// Finally, update the metadata on the cache entry.
1062
nsAutoCString newValue;
1063
MakeMetadataEntry(hitCount, lastHit, newFlags, newValue);
1064
entry->SetMetaDataElement(key, newValue.BeginReading());
1065
}
1066
1067
uint32_t Predictor::ClampedPrefetchRollingLoadCount() {
1068
int32_t n = StaticPrefs::network_predictor_prefetch_rolling_load_count();
1069
if (n < 0) {
1070
return 0;
1071
}
1072
if (n > kMaxPrefetchRollingLoadCount) {
1073
return kMaxPrefetchRollingLoadCount;
1074
}
1075
return n;
1076
}
1077
1078
void Predictor::CalculatePredictions(nsICacheEntry* entry, nsIURI* referrer,
1079
uint32_t lastLoad, uint32_t loadCount,
1080
int32_t globalDegradation, bool fullUri) {
1081
MOZ_ASSERT(NS_IsMainThread());
1082
1083
// Since the visitor gets called under a cache lock, all we do there is get
1084
// copies of the keys/values we care about, and then do the real work here
1085
entry->VisitMetaData(this);
1086
nsTArray<nsCString> keysToOperateOn, valuesToOperateOn;
1087
keysToOperateOn.SwapElements(mKeysToOperateOn);
1088
valuesToOperateOn.SwapElements(mValuesToOperateOn);
1089
1090
MOZ_ASSERT(keysToOperateOn.Length() == valuesToOperateOn.Length());
1091
for (size_t i = 0; i < keysToOperateOn.Length(); ++i) {
1092
const char* key = keysToOperateOn[i].BeginReading();
1093
const char* value = valuesToOperateOn[i].BeginReading();
1094
1095
nsCString uri;
1096
uint32_t hitCount, lastHit, flags;
1097
if (!ParseMetaDataEntry(key, value, uri, hitCount, lastHit, flags)) {
1098
// This failed, get rid of it so we don't waste space
1099
entry->SetMetaDataElement(key, nullptr);
1100
continue;
1101
}
1102
1103
int32_t confidence = CalculateConfidence(hitCount, loadCount, lastHit,
1104
lastLoad, globalDegradation);
1105
if (fullUri) {
1106
UpdateRollingLoadCount(entry, flags, key, hitCount, lastHit);
1107
}
1108
PREDICTOR_LOG(("CalculatePredictions key=%s value=%s confidence=%d", key,
1109
value, confidence));
1110
PrefetchIgnoreReason reason = PREFETCH_OK;
1111
if (!fullUri) {
1112
// Not full URI - don't prefetch! No sense in it!
1113
PREDICTOR_LOG((" forcing non-cacheability - not full URI"));
1114
if (flags & FLAG_PREFETCHABLE) {
1115
// This only applies if we had somehow otherwise marked this
1116
// prefetchable.
1117
reason = NOT_FULL_URI;
1118
}
1119
flags &= ~FLAG_PREFETCHABLE;
1120
} else if (!referrer) {
1121
// No referrer means we can't prefetch, so pretend it's non-cacheable,
1122
// no matter what.
1123
PREDICTOR_LOG((" forcing non-cacheability - no referrer"));
1124
if (flags & FLAG_PREFETCHABLE) {
1125
// This only applies if we had somehow otherwise marked this
1126
// prefetchable.
1127
reason = NO_REFERRER;
1128
}
1129
flags &= ~FLAG_PREFETCHABLE;
1130
} else {
1131
uint32_t expectedRollingLoadCount =
1132
(1 << ClampedPrefetchRollingLoadCount()) - 1;
1133
expectedRollingLoadCount <<= kRollingLoadOffset;
1134
if ((flags & expectedRollingLoadCount) != expectedRollingLoadCount) {
1135
PREDICTOR_LOG((" forcing non-cacheability - missed a load"));
1136
if (flags & FLAG_PREFETCHABLE) {
1137
// This only applies if we had somehow otherwise marked this
1138
// prefetchable.
1139
reason = MISSED_A_LOAD;
1140
}
1141
flags &= ~FLAG_PREFETCHABLE;
1142
}
1143
}
1144
1145
PREDICTOR_LOG((" setting up prediction"));
1146
SetupPrediction(confidence, flags, uri, reason);
1147
}
1148
}
1149
1150
// (Maybe) adds a predictive action to the prediction runner, based on our
1151
// calculated confidence for the subresource in question.
1152
void Predictor::SetupPrediction(int32_t confidence, uint32_t flags,
1153
const nsCString& uri,
1154
PrefetchIgnoreReason earlyReason) {
1155
MOZ_ASSERT(NS_IsMainThread());
1156
1157
nsresult rv = NS_OK;
1158
PREDICTOR_LOG(
1159
("SetupPrediction enable-prefetch=%d prefetch-min-confidence=%d "
1160
"preconnect-min-confidence=%d preresolve-min-confidence=%d "
1161
"flags=%d confidence=%d uri=%s",
1162
StaticPrefs::network_predictor_enable_prefetch(),
1163
StaticPrefs::network_predictor_prefetch_min_confidence(),
1164
StaticPrefs::network_predictor_preconnect_min_confidence(),
1165
StaticPrefs::network_predictor_preresolve_min_confidence(), flags,
1166
confidence, uri.get()));
1167
1168
bool prefetchOk = !!(flags & FLAG_PREFETCHABLE);
1169
PrefetchIgnoreReason reason = earlyReason;
1170
if (prefetchOk && !StaticPrefs::network_predictor_enable_prefetch()) {
1171
prefetchOk = false;
1172
reason = PREFETCH_DISABLED;
1173
} else if (prefetchOk && !ClampedPrefetchRollingLoadCount() &&
1174
confidence <
1175
StaticPrefs::network_predictor_prefetch_min_confidence()) {
1176
prefetchOk = false;
1177
if (!ClampedPrefetchRollingLoadCount()) {
1178
reason = PREFETCH_DISABLED_VIA_COUNT;
1179
} else {
1180
reason = CONFIDENCE_TOO_LOW;
1181
}
1182
}
1183
1184
// prefetchOk == false and reason == PREFETCH_OK indicates that the reason
1185
// we aren't prefetching this item is because it was marked un-prefetchable in
1186
// our metadata. We already have separate telemetry on that decision, so we
1187
// aren't going to accumulate more here. Right now we only care about why
1188
// something we had marked prefetchable isn't being prefetched.
1189
if (!prefetchOk && reason != PREFETCH_OK) {
1190
Telemetry::Accumulate(Telemetry::PREDICTOR_PREFETCH_IGNORE_REASON, reason);
1191
}
1192
1193
if (prefetchOk) {
1194
nsCOMPtr<nsIURI> prefetchURI;
1195
rv = NS_NewURI(getter_AddRefs(prefetchURI), uri);
1196
if (NS_SUCCEEDED(rv)) {
1197
mPrefetches.AppendElement(prefetchURI);
1198
}
1199
} else if (confidence >=
1200
StaticPrefs::network_predictor_preconnect_min_confidence()) {
1201
nsCOMPtr<nsIURI> preconnectURI;
1202
rv = NS_NewURI(getter_AddRefs(preconnectURI), uri);
1203
if (NS_SUCCEEDED(rv)) {
1204
mPreconnects.AppendElement(preconnectURI);
1205
}
1206
} else if (confidence >=
1207
StaticPrefs::network_predictor_preresolve_min_confidence()) {
1208
nsCOMPtr<nsIURI> preresolveURI;
1209
rv = NS_NewURI(getter_AddRefs(preresolveURI), uri);
1210
if (NS_SUCCEEDED(rv)) {
1211
mPreresolves.AppendElement(preresolveURI);
1212
}
1213
}
1214
1215
if (NS_FAILED(rv)) {
1216
PREDICTOR_LOG(
1217
(" NS_NewURI returned 0x%" PRIx32, static_cast<uint32_t>(rv)));
1218
}
1219
}
1220
1221
nsresult Predictor::Prefetch(nsIURI* uri, nsIURI* referrer,
1222
const OriginAttributes& originAttributes,
1223
nsINetworkPredictorVerifier* verifier) {
1224
nsAutoCString strUri, strReferrer;
1225
uri->GetAsciiSpec(strUri);
1226
referrer->GetAsciiSpec(strReferrer);
1227
PREDICTOR_LOG(("Predictor::Prefetch uri=%s referrer=%s verifier=%p",
1228
strUri.get(), strReferrer.get(), verifier));
1229
nsCOMPtr<nsIChannel> channel;
1230
nsresult rv = NS_NewChannel(
1231
getter_AddRefs(channel), uri, nsContentUtils::GetSystemPrincipal(),
1232
nsILoadInfo::SEC_ALLOW_CROSS_ORIGIN_DATA_IS_NULL,
1233
nsIContentPolicy::TYPE_OTHER, nullptr, /* nsICookieSettings */
1234
nullptr, /* aPerformanceStorage */
1235
nullptr, /* aLoadGroup */
1236
nullptr, /* aCallbacks */
1237
nsIRequest::LOAD_BACKGROUND);
1238
1239
if (NS_FAILED(rv)) {
1240
PREDICTOR_LOG(
1241
(" NS_NewChannel failed rv=0x%" PRIX32, static_cast<uint32_t>(rv)));
1242
return rv;
1243
}
1244
1245
nsCOMPtr<nsILoadInfo> loadInfo = channel->LoadInfo();
1246
rv = loadInfo->SetOriginAttributes(originAttributes);
1247
1248
if (NS_FAILED(rv)) {
1249
PREDICTOR_LOG(
1250
(" Set originAttributes into loadInfo failed rv=0x%" PRIX32,
1251
static_cast<uint32_t>(rv)));
1252
return rv;
1253
}
1254
1255
nsCOMPtr<nsIHttpChannel> httpChannel;
1256
httpChannel = do_QueryInterface(channel);
1257
if (!httpChannel) {
1258
PREDICTOR_LOG((" Could not get HTTP Channel from new channel!"));
1259
return NS_ERROR_UNEXPECTED;
1260
}
1261
1262
nsCOMPtr<nsIReferrerInfo> referrerInfo = new ReferrerInfo(referrer);
1263
rv = httpChannel->SetReferrerInfoWithoutClone(referrerInfo);
1264
NS_ENSURE_SUCCESS(rv, rv);
1265
// XXX - set a header here to indicate this is a prefetch?
1266
1267
nsCOMPtr<nsIStreamListener> listener =
1268
new PrefetchListener(verifier, uri, this);
1269
PREDICTOR_LOG((" calling AsyncOpen listener=%p channel=%p", listener.get(),
1270
channel.get()));
1271
rv = channel->AsyncOpen(listener);
1272
if (NS_FAILED(rv)) {
1273
PREDICTOR_LOG(
1274
(" AsyncOpen failed rv=0x%" PRIX32, static_cast<uint32_t>(rv)));
1275
}
1276
1277
return rv;
1278
}
1279
1280
// Runs predictions that have been set up.
1281
bool Predictor::RunPredictions(nsIURI* referrer,
1282
const OriginAttributes& originAttributes,
1283
nsINetworkPredictorVerifier* verifier) {
1284
MOZ_ASSERT(NS_IsMainThread(), "Running prediction off main thread");
1285
1286
PREDICTOR_LOG(("Predictor::RunPredictions"));
1287
1288
bool predicted = false;
1289
uint32_t len, i;
1290
1291
nsTArray<nsCOMPtr<nsIURI>> prefetches, preconnects, preresolves;
1292
prefetches.SwapElements(mPrefetches);
1293
preconnects.SwapElements(mPreconnects);
1294
preresolves.SwapElements(mPreresolves);
1295
1296
Telemetry::AutoCounter<Telemetry::PREDICTOR_TOTAL_PREDICTIONS>
1297
totalPredictions;
1298
Telemetry::AutoCounter<Telemetry::PREDICTOR_TOTAL_PREFETCHES> totalPrefetches;
1299
Telemetry::AutoCounter<Telemetry::PREDICTOR_TOTAL_PRECONNECTS>
1300
totalPreconnects;
1301
Telemetry::AutoCounter<Telemetry::PREDICTOR_TOTAL_PRERESOLVES>
1302
totalPreresolves;
1303
1304
len = prefetches.Length();
1305
for (i = 0; i < len; ++i) {
1306
PREDICTOR_LOG((" doing prefetch"));
1307
nsCOMPtr<nsIURI> uri = prefetches[i];
1308
if (NS_SUCCEEDED(Prefetch(uri, referrer, originAttributes, verifier))) {
1309
++totalPredictions;
1310
++totalPrefetches;
1311
predicted = true;
1312
}
1313
}
1314
1315
len = preconnects.Length();
1316
for (i = 0; i < len; ++i) {
1317
PREDICTOR_LOG((" doing preconnect"));
1318
nsCOMPtr<nsIURI> uri = preconnects[i];
1319
++totalPredictions;
1320
++totalPreconnects;
1321
nsCOMPtr<nsIPrincipal> principal =
1322
BasePrincipal::CreateContentPrincipal(uri, originAttributes);
1323
mSpeculativeService->SpeculativeConnect(uri, principal, this);
1324
predicted = true;
1325
if (verifier) {
1326
PREDICTOR_LOG((" sending preconnect verification"));
1327
verifier->OnPredictPreconnect(uri);
1328
}
1329
}
1330
1331
len = preresolves.Length();
1332
for (i = 0; i < len; ++i) {
1333
nsCOMPtr<nsIURI> uri = preresolves[i];
1334
++totalPredictions;
1335
++totalPreresolves;
1336
nsAutoCString hostname;
1337
uri->GetAsciiHost(hostname);
1338
PREDICTOR_LOG((" doing preresolve %s", hostname.get()));
1339
nsCOMPtr<nsICancelable> tmpCancelable;
1340
mDnsService->AsyncResolveNative(hostname,
1341
(nsIDNSService::RESOLVE_PRIORITY_MEDIUM |
1342
nsIDNSService::RESOLVE_SPECULATE),
1343
mDNSListener, nullptr, originAttributes,
1344
getter_AddRefs(tmpCancelable));
1345
1346
// Fetch esni keys if needed.
1347
if (sEsniEnabled && uri->SchemeIs("https")) {
1348
nsAutoCString esniHost;
1349
esniHost.Append("_esni.");
1350
esniHost.Append(hostname);
1351
mDnsService->AsyncResolveByTypeNative(
1352
esniHost, nsIDNSService::RESOLVE_TYPE_TXT,
1353
(nsIDNSService::RESOLVE_PRIORITY_MEDIUM |
1354
nsIDNSService::RESOLVE_SPECULATE),
1355
mDNSListener, nullptr, originAttributes,
1356
getter_AddRefs(tmpCancelable));
1357
}
1358
1359
predicted = true;
1360
if (verifier) {
1361
PREDICTOR_LOG((" sending preresolve verification"));
1362
verifier->OnPredictDNS(uri);
1363
}
1364
}
1365
1366
return predicted;
1367
}
1368
1369
// Find out if a top-level page is likely to redirect.
1370
bool Predictor::WouldRedirect(nsICacheEntry* entry, uint32_t loadCount,
1371
uint32_t lastLoad, int32_t globalDegradation,
1372
nsIURI** redirectURI) {
1373
// TODO - not doing redirects for first go around
1374
MOZ_ASSERT(NS_IsMainThread());
1375
1376
return false;
1377
}
1378
1379
NS_IMETHODIMP
1380
Predictor::Learn(nsIURI* targetURI, nsIURI* sourceURI,
1381
PredictorLearnReason reason, JS::HandleValue originAttributes,
1382
JSContext* aCx) {
1383
OriginAttributes attrs;
1384
1385
if (!originAttributes.isObject() || !attrs.Init(aCx, originAttributes)) {
1386
return NS_ERROR_INVALID_ARG;
1387
}
1388
1389
return LearnNative(targetURI, sourceURI, reason, attrs);
1390
}
1391
1392
// Called from the main thread to update the database
1393
NS_IMETHODIMP
1394
Predictor::LearnNative(nsIURI* targetURI, nsIURI* sourceURI,
1395
PredictorLearnReason reason,
1396
const OriginAttributes& originAttributes) {
1397
MOZ_ASSERT(NS_IsMainThread(),
1398
"Predictor interface methods must be called on the main thread");
1399
1400
PREDICTOR_LOG(("Predictor::Learn"));
1401
1402
if (IsNeckoChild()) {
1403
MOZ_DIAGNOSTIC_ASSERT(gNeckoChild);
1404
1405
PREDICTOR_LOG((" called on child process"));
1406
1407
RefPtr<PredictorLearnRunnable> runnable = new PredictorLearnRunnable(
1408
targetURI, sourceURI, reason, originAttributes);
1409
SystemGroup::Dispatch(TaskCategory::Other, runnable.forget());
1410
1411
return NS_OK;
1412
}
1413
1414
PREDICTOR_LOG((" called on parent process"));
1415
1416
if (!mInitialized) {
1417
PREDICTOR_LOG((" not initialized"));
1418
return NS_OK;
1419
}
1420
1421
if (!StaticPrefs::network_predictor_enabled()) {
1422
PREDICTOR_LOG((" not enabled"));
1423
return NS_OK;
1424
}
1425
1426
if (originAttributes.mPrivateBrowsingId > 0) {
1427
// Don't want to do anything in PB mode
1428
PREDICTOR_LOG((" in PB mode"));
1429
return NS_OK;
1430
}
1431
1432
if (!IsNullOrHttp(targetURI) || !IsNullOrHttp(sourceURI)) {
1433
PREDICTOR_LOG((" got non-HTTP[S] URI"));
1434
return NS_ERROR_INVALID_ARG;
1435
}
1436
1437
nsCOMPtr<nsIURI> targetOrigin;
1438
nsCOMPtr<nsIURI> sourceOrigin;
1439
nsCOMPtr<nsIURI> uriKey;
1440
nsCOMPtr<nsIURI> originKey;
1441
nsresult rv;
1442
1443
switch (reason) {
1444
case nsINetworkPredictor::LEARN_LOAD_TOPLEVEL:
1445
if (!targetURI || sourceURI) {
1446
PREDICTOR_LOG((" load toplevel invalid URI state"));
1447
return NS_ERROR_INVALID_ARG;
1448
}
1449
rv = ExtractOrigin(targetURI, getter_AddRefs(targetOrigin));
1450
NS_ENSURE_SUCCESS(rv, rv);
1451
uriKey = targetURI;
1452
originKey = targetOrigin;
1453
break;
1454
case nsINetworkPredictor::LEARN_STARTUP:
1455
if (!targetURI || sourceURI) {
1456
PREDICTOR_LOG((" startup invalid URI state"));
1457
return NS_ERROR_INVALID_ARG;
1458
}
1459
rv = ExtractOrigin(targetURI, getter_AddRefs(targetOrigin));
1460
NS_ENSURE_SUCCESS(rv, rv);
1461
uriKey = mStartupURI;
1462
originKey = mStartupURI;
1463
break;
1464
case nsINetworkPredictor::LEARN_LOAD_REDIRECT:
1465
case nsINetworkPredictor::LEARN_LOAD_SUBRESOURCE:
1466
if (!targetURI || !sourceURI) {
1467
PREDICTOR_LOG((" redirect/subresource invalid URI state"));
1468
return NS_ERROR_INVALID_ARG;
1469
}
1470
rv = ExtractOrigin(targetURI, getter_AddRefs(targetOrigin));
1471
NS_ENSURE_SUCCESS(rv, rv);
1472
rv = ExtractOrigin(sourceURI, getter_AddRefs(sourceOrigin));
1473
NS_ENSURE_SUCCESS(rv, rv);
1474
uriKey = sourceURI;
1475
originKey = sourceOrigin;
1476
break;
1477
default:
1478
PREDICTOR_LOG((" invalid reason"));
1479
return NS_ERROR_INVALID_ARG;
1480
}
1481
1482
Telemetry::AutoCounter<Telemetry::PREDICTOR_LEARN_ATTEMPTS> learnAttempts;
1483
++learnAttempts;
1484
1485
Predictor::Reason argReason;
1486
argReason.mLearn = reason;
1487
1488
// We always open the full uri (general cache) entry first, so we don't gum up
1489
// the works waiting on predictor-only entries to open
1490
RefPtr<Predictor::Action> uriAction = new Predictor::Action(
1491
Predictor::Action::IS_FULL_URI, Predictor::Action::DO_LEARN, argReason,
1492
targetURI, sourceURI, nullptr, this);
1493
nsAutoCString uriKeyStr, targetUriStr, sourceUriStr;
1494
uriKey->GetAsciiSpec(uriKeyStr);
1495
targetURI->GetAsciiSpec(targetUriStr);
1496
if (sourceURI) {
1497
sourceURI->GetAsciiSpec(sourceUriStr);
1498
}
1499
PREDICTOR_LOG(
1500
(" Learn uriKey=%s targetURI=%s sourceURI=%s reason=%d "
1501
"action=%p",
1502
uriKeyStr.get(), targetUriStr.get(), sourceUriStr.get(), reason,
1503
uriAction.get()));
1504
1505
nsCOMPtr<nsICacheStorage> cacheDiskStorage;
1506
1507
RefPtr<LoadContextInfo> lci = new LoadContextInfo(false, originAttributes);
1508
1509
rv = mCacheStorageService->DiskCacheStorage(lci, false,
1510
getter_AddRefs(cacheDiskStorage));
1511
NS_ENSURE_SUCCESS(rv, rv);
1512
1513
// For learning full URI things, we *always* open readonly and secretly, as we
1514
// rely on actual pageloads to update the entry's metadata for us.
1515
uint32_t uriOpenFlags = nsICacheStorage::OPEN_READONLY |
1516
nsICacheStorage::OPEN_SECRETLY |
1517
nsICacheStorage::CHECK_MULTITHREADED;
1518
if (reason == nsINetworkPredictor::LEARN_LOAD_TOPLEVEL) {
1519
// Learning for toplevel we want to open the full uri entry priority, since
1520
// it's likely this entry will be used soon anyway, and we want this to be
1521
// opened ASAP.
1522
uriOpenFlags |= nsICacheStorage::OPEN_PRIORITY;
1523
}
1524
cacheDiskStorage->AsyncOpenURI(uriKey, EmptyCString(), uriOpenFlags,
1525
uriAction);
1526
1527
// Now we open the origin-only (and therefore predictor-only) entry
1528
RefPtr<Predictor::Action> originAction = new Predictor::Action(
1529
Predictor::Action::IS_ORIGIN, Predictor::Action::DO_LEARN, argReason,
1530
targetOrigin, sourceOrigin, nullptr, this);
1531
nsAutoCString originKeyStr, targetOriginStr, sourceOriginStr;
1532
originKey->GetAsciiSpec(originKeyStr);
1533
targetOrigin->GetAsciiSpec(targetOriginStr);
1534
if (sourceOrigin) {
1535
sourceOrigin->GetAsciiSpec(sourceOriginStr);
1536
}
1537
PREDICTOR_LOG(
1538
(" Learn originKey=%s targetOrigin=%s sourceOrigin=%s reason=%d "
1539
"action=%p",
1540
originKeyStr.get(), targetOriginStr.get(), sourceOriginStr.get(), reason,
1541
originAction.get()));
1542
uint32_t originOpenFlags;
1543
if (reason == nsINetworkPredictor::LEARN_LOAD_TOPLEVEL) {
1544
// This is the only case when we want to update the 'last used' metadata on
1545
// the cache entry we're getting. This only applies to predictor-specific
1546
// entries.
1547
originOpenFlags =
1548
nsICacheStorage::OPEN_NORMALLY | nsICacheStorage::CHECK_MULTITHREADED;
1549
} else {
1550
originOpenFlags = nsICacheStorage::OPEN_READONLY |
1551
nsICacheStorage::OPEN_SECRETLY |
1552
nsICacheStorage::CHECK_MULTITHREADED;
1553
}
1554
cacheDiskStorage->AsyncOpenURI(originKey,
1555
NS_LITERAL_CSTRING(PREDICTOR_ORIGIN_EXTENSION),
1556
originOpenFlags, originAction);
1557
1558
PREDICTOR_LOG(("Predictor::Learn returning"));
1559
return NS_OK;
1560
}
1561
1562
void Predictor::LearnInternal(PredictorLearnReason reason, nsICacheEntry* entry,
1563
bool isNew, bool fullUri, nsIURI* targetURI,
1564
nsIURI* sourceURI) {
1565
MOZ_ASSERT(NS_IsMainThread());
1566
1567
PREDICTOR_LOG(("Predictor::LearnInternal"));
1568
1569
nsCString junk;
1570
if (!fullUri && reason == nsINetworkPredictor::LEARN_LOAD_TOPLEVEL &&
1571
NS_FAILED(
1572
entry->GetMetaDataElement(SEEN_META_DATA, getter_Copies(junk)))) {
1573
// This is an origin-only entry that we haven't seen before. Let's mark it
1574
// as seen.
1575
PREDICTOR_LOG((" marking new origin entry as seen"));
1576
nsresult rv = entry->SetMetaDataElement(SEEN_META_DATA, "1");
1577
if (NS_FAILED(rv)) {
1578
PREDICTOR_LOG((" failed to mark origin entry seen"));
1579
return;
1580
}
1581
1582
// Need to ensure someone else can get to the entry if necessary
1583
entry->MetaDataReady();
1584
return;
1585
}
1586
1587
switch (reason) {
1588
case nsINetworkPredictor::LEARN_LOAD_TOPLEVEL:
1589
// This case only exists to be used during tests - code outside the
1590
// predictor tests should NEVER call Learn with LEARN_LOAD_TOPLEVEL.
1591
// The predictor xpcshell test needs this branch, however, because we
1592
// have no real page loads in xpcshell, and this is how we fake it up
1593
// so that all the work that normally happens behind the scenes in a
1594
// page load can be done for testing purposes.
1595
if (fullUri && StaticPrefs::network_predictor_doing_tests()) {
1596
PREDICTOR_LOG(
1597
(" WARNING - updating rolling load count. "
1598
"If you see this outside tests, you did it wrong"));
1599
1600
// Since the visitor gets called under a cache lock, all we do there is
1601
// get copies of the keys/values we care about, and then do the real
1602
// work here
1603
entry->VisitMetaData(this);
1604
nsTArray<nsCString> keysToOperateOn, valuesToOperateOn;
1605
keysToOperateOn.SwapElements(mKeysToOperateOn);
1606
valuesToOperateOn.SwapElements(mValuesToOperateOn);
1607
1608
MOZ_ASSERT(keysToOperateOn.Length() == valuesToOperateOn.Length());
1609
for (size_t i = 0; i < keysToOperateOn.Length(); ++i) {
1610
const char* key = keysToOperateOn[i].BeginReading();
1611
const char* value = valuesToOperateOn[i].BeginReading();
1612
1613
nsCString uri;
1614
uint32_t hitCount, lastHit, flags;
1615
if (!ParseMetaDataEntry(key, value, uri, hitCount, lastHit, flags)) {
1616
// This failed, get rid of it so we don't waste space
1617
entry->SetMetaDataElement(key, nullptr);
1618
continue;
1619
}
1620
UpdateRollingLoadCount(entry, flags, key, hitCount, lastHit);
1621
}
1622
} else {
1623
PREDICTOR_LOG((" nothing to do for toplevel"));
1624
}
1625
break;
1626
case nsINetworkPredictor::LEARN_LOAD_REDIRECT:
1627
if (fullUri) {
1628
LearnForRedirect(entry, targetURI);
1629
}
1630
break;
1631
case nsINetworkPredictor::LEARN_LOAD_SUBRESOURCE:
1632
LearnForSubresource(entry, targetURI);
1633
break;
1634
case nsINetworkPredictor::LEARN_STARTUP:
1635
LearnForStartup(entry, targetURI);
1636
break;
1637
default:
1638
PREDICTOR_LOG((" unexpected reason value"));
1639
MOZ_ASSERT(false, "Got unexpected value for learn reason!");
1640
}
1641
}
1642
1643
NS_IMPL_ISUPPORTS(Predictor::SpaceCleaner, nsICacheEntryMetaDataVisitor)
1644
1645
NS_IMETHODIMP
1646
Predictor::SpaceCleaner::OnMetaDataElement(const char* key, const char* value) {
1647
MOZ_ASSERT(NS_IsMainThread());
1648
1649
if (!IsURIMetadataElement(key)) {
1650
// This isn't a bit of metadata we care about
1651
return NS_OK;
1652
}
1653
1654
nsCString uri;
1655
uint32_t hitCount, lastHit, flags;
1656
bool ok =
1657
mPredictor->ParseMetaDataEntry(key, value, uri, hitCount, lastHit, flags);
1658
1659
if (!ok) {
1660
// Couldn't parse this one, just get rid of it
1661
nsCString nsKey;
1662
nsKey.AssignASCII(key);
1663
mLongKeysToDelete.AppendElement(nsKey);
1664
return NS_OK;
1665
}
1666
1667
uint32_t uriLength = uri.Length();
1668
if (uriLength > StaticPrefs::network_predictor_max_uri_length()) {
1669
// Default to getting rid of URIs that are too long and were put in before
1670
// we had our limit on URI length, in order to free up some space.
1671
nsCString nsKey;
1672
nsKey.AssignASCII(key);
1673
mLongKeysToDelete.AppendElement(nsKey);
1674
return NS_OK;
1675
}
1676
1677
if (!mLRUKeyToDelete || lastHit < mLRUStamp) {
1678
mLRUKeyToDelete = key;
1679
mLRUStamp = lastHit;
1680
}
1681
1682
return NS_OK;
1683
}
1684
1685
void Predictor::SpaceCleaner::Finalize(nsICacheEntry* entry) {
1686
MOZ_ASSERT(NS_IsMainThread());
1687
1688
if (mLRUKeyToDelete) {
1689
entry->SetMetaDataElement(mLRUKeyToDelete, nullptr);
1690
}
1691
1692
for (size_t i = 0; i < mLongKeysToDelete.Length(); ++i) {
1693
entry->SetMetaDataElement(mLongKeysToDelete[i].BeginReading(), nullptr);
1694
}
1695
}
1696
1697
// Called when a subresource has been hit from a top-level load. Uses the two
1698
// helper functions above to update the database appropriately.
1699
void Predictor::LearnForSubresource(nsICacheEntry* entry, nsIURI* targetURI) {
1700
MOZ_ASSERT(NS_IsMainThread());
1701
1702
PREDICTOR_LOG(("Predictor::LearnForSubresource"));
1703
1704
uint32_t lastLoad;
1705
nsresult rv = entry->GetLastFetched(&lastLoad);
1706
RETURN_IF_FAILED(rv);
1707
1708
int32_t loadCount;
1709
rv = entry->GetFetchCount(&loadCount);
1710
RETURN_IF_FAILED(rv);
1711
1712
nsCString key;
1713
key.AssignLiteral(META_DATA_PREFIX);
1714
nsCString uri;
1715
targetURI->GetAsciiSpec(uri);
1716
key.Append(uri);
1717
if (uri.Length() > StaticPrefs::network_predictor_max_uri_length()) {
1718
// We do this to conserve space/prevent OOMs
1719
PREDICTOR_LOG((" uri too long!"));
1720
entry->SetMetaDataElement(key.BeginReading(), nullptr);
1721
return;
1722
}
1723
1724
nsCString value;
1725
rv = entry->GetMetaDataElement(key.BeginReading(), getter_Copies(value));
1726
1727
uint32_t hitCount, lastHit, flags;
1728
bool isNewResource =
1729
(NS_FAILED(rv) ||
1730
!ParseMetaDataEntry(key.BeginReading(), value.BeginReading(), uri,
1731
hitCount, lastHit, flags));
1732
1733
int32_t resourceCount = 0;
1734
if (isNewResource) {
1735
// This is a new addition
1736
PREDICTOR_LOG((" new resource"));
1737
nsCString s;
1738
rv = entry->GetMetaDataElement(RESOURCE_META_DATA, getter_Copies(s));
1739
if (NS_SUCCEEDED(rv)) {
1740
resourceCount = atoi(s.BeginReading());
1741
}
1742
if (resourceCount >=
1743
StaticPrefs::network_predictor_max_resources_per_entry()) {
1744
RefPtr<Predictor::SpaceCleaner> cleaner =
1745
new Predictor::SpaceCleaner(this);
1746
entry->VisitMetaData(cleaner);
1747
cleaner->Finalize(entry);
1748
} else {
1749
++resourceCount;
1750
}
1751
nsAutoCString count;
1752
count.AppendInt(resourceCount);
1753
rv = entry->SetMetaDataElement(RESOURCE_META_DATA, count.BeginReading());
1754
if (NS_FAILED(rv)) {
1755
PREDICTOR_LOG((" failed to update resource count"));
1756
return;
1757
}
1758
hitCount = 1;
1759
flags = 0;
1760
} else {
1761
PREDICTOR_LOG((" existing resource"));
1762
hitCount = std::min(hitCount + 1, static_cast<uint32_t>(loadCount));
1763
}
1764
1765
// Update the rolling load count to mark this sub-resource as seen on the
1766
// most-recent pageload so it can be eligible for prefetch (assuming all
1767
// the other stars align).
1768
flags |= (1 << kRollingLoadOffset);
1769
1770
nsCString newValue;
1771
MakeMetadataEntry(hitCount, lastLoad, flags, newValue);
1772
rv = entry->SetMetaDataElement(key.BeginReading(), newValue.BeginReading());
1773
PREDICTOR_LOG(
1774
(" SetMetaDataElement -> 0x%08" PRIX32, static_cast<uint32_t>(rv)));
1775
if (NS_FAILED(rv) && isNewResource) {