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