Coverage Report

Created: 2018-09-25 14:53

/src/mozilla-central/netwerk/base/Predictor.cpp
Line
Count
Source (jump to first uncovered line)
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 "nsIDocument.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
57
using namespace mozilla;
58
59
namespace mozilla {
60
namespace net {
61
62
Predictor *Predictor::sSelf = nullptr;
63
64
static LazyLogModule gPredictorLog("NetworkPredictor");
65
66
0
#define PREDICTOR_LOG(args) MOZ_LOG(gPredictorLog, mozilla::LogLevel::Debug, args)
67
68
#define RETURN_IF_FAILED(_rv) \
69
0
  do { \
70
0
    if (NS_FAILED(_rv)) { \
71
0
      return; \
72
0
    } \
73
0
  } while (0)
74
75
0
#define NOW_IN_SECONDS() static_cast<uint32_t>(PR_Now() / PR_USEC_PER_SEC)
76
77
static const char PREDICTOR_CLEANED_UP_PREF[] = "network.predictor.cleaned-up";
78
79
// All these time values are in sec
80
static const uint32_t ONE_DAY = 86400U;
81
static const uint32_t ONE_WEEK = 7U * ONE_DAY;
82
static const uint32_t ONE_MONTH = 30U * ONE_DAY;
83
static const uint32_t ONE_YEAR = 365U * ONE_DAY;
84
85
static const uint32_t STARTUP_WINDOW = 5U * 60U; // 5min
86
87
// Version of metadata entries we expect
88
static const uint32_t METADATA_VERSION = 1;
89
90
// Flags available in entries
91
// FLAG_PREFETCHABLE - we have determined that this item is eligible for prefetch
92
static const uint32_t FLAG_PREFETCHABLE = 1 << 0;
93
94
// We save 12 bits in the "flags" section of our metadata for actual flags, the
95
// rest are to keep track of a rolling count of which loads a resource has been
96
// used on to determine if we can prefetch that resource or not;
97
static const uint8_t kRollingLoadOffset = 12;
98
static const int32_t kMaxPrefetchRollingLoadCount = 20;
99
static const uint32_t kFlagsMask = ((1 << kRollingLoadOffset) - 1);
100
101
static bool sEsniEnabled = false;
102
103
// ID Extensions for cache entries
104
0
#define PREDICTOR_ORIGIN_EXTENSION "predictor-origin"
105
106
// Get the full origin (scheme, host, port) out of a URI (maybe should be part
107
// of nsIURI instead?)
108
static nsresult
109
ExtractOrigin(nsIURI *uri, nsIURI **originUri, nsIIOService *ioService)
110
0
{
111
0
  nsAutoCString s;
112
0
  s.Truncate();
113
0
  nsresult rv = nsContentUtils::GetASCIIOrigin(uri, s);
114
0
  NS_ENSURE_SUCCESS(rv, rv);
115
0
116
0
  return NS_NewURI(originUri, s, nullptr, nullptr, ioService);
117
0
}
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
122
IsNullOrHttp(nsIURI *uri)
123
0
{
124
0
  if (!uri) {
125
0
    return true;
126
0
  }
127
0
128
0
  bool isHTTP = false;
129
0
  uri->SchemeIs("http", &isHTTP);
130
0
  if (!isHTTP) {
131
0
    uri->SchemeIs("https", &isHTTP);
132
0
  }
133
0
134
0
  return isHTTP;
135
0
}
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,
147
                                         nsresult status)
148
0
{
149
0
  return NS_OK;
150
0
}
151
152
NS_IMETHODIMP
153
Predictor::DNSListener::OnLookupByTypeComplete(nsICancelable *request,
154
                                               nsIDNSByTypeRecord *res,
155
                                               nsresult status)
156
0
{
157
0
  return NS_OK;
158
0
}
159
160
// Class to proxy important information from the initial predictor call through
161
// the cache API and back into the internals of the predictor. We can't use the
162
// predictor itself, as it may have multiple actions in-flight, and each action
163
// has different parameters.
164
NS_IMPL_ISUPPORTS(Predictor::Action, nsICacheEntryOpenCallback);
165
166
Predictor::Action::Action(bool fullUri, bool predict,
167
                          Predictor::Reason reason,
168
                          nsIURI *targetURI, nsIURI *sourceURI,
169
                          nsINetworkPredictorVerifier *verifier,
170
                          Predictor *predictor)
171
  :mFullUri(fullUri)
172
  ,mPredict(predict)
173
  ,mTargetURI(targetURI)
174
  ,mSourceURI(sourceURI)
175
  ,mVerifier(verifier)
176
  ,mStackCount(0)
177
  ,mPredictor(predictor)
178
0
{
179
0
  mStartTime = TimeStamp::Now();
180
0
  if (mPredict) {
181
0
    mPredictReason = reason.mPredict;
182
0
  } else {
183
0
    mLearnReason = reason.mLearn;
184
0
  }
185
0
}
186
187
Predictor::Action::Action(bool fullUri, bool predict,
188
                          Predictor::Reason reason,
189
                          nsIURI *targetURI, nsIURI *sourceURI,
190
                          nsINetworkPredictorVerifier *verifier,
191
                          Predictor *predictor, uint8_t stackCount)
192
  :mFullUri(fullUri)
193
  ,mPredict(predict)
194
  ,mTargetURI(targetURI)
195
  ,mSourceURI(sourceURI)
196
  ,mVerifier(verifier)
197
  ,mStackCount(stackCount)
198
  ,mPredictor(predictor)
199
0
{
200
0
  mStartTime = TimeStamp::Now();
201
0
  if (mPredict) {
202
0
    mPredictReason = reason.mPredict;
203
0
  } else {
204
0
    mLearnReason = reason.mLearn;
205
0
  }
206
0
}
207
208
NS_IMETHODIMP
209
Predictor::Action::OnCacheEntryCheck(nsICacheEntry *entry,
210
                                     nsIApplicationCache *appCache,
211
                                     uint32_t *result)
212
0
{
213
0
  *result = nsICacheEntryOpenCallback::ENTRY_WANTED;
214
0
  return NS_OK;
215
0
}
216
217
NS_IMETHODIMP
218
Predictor::Action::OnCacheEntryAvailable(nsICacheEntry *entry, bool isNew,
219
                                         nsIApplicationCache *appCache,
220
                                         nsresult result)
221
0
{
222
0
  MOZ_ASSERT(NS_IsMainThread(), "Got cache entry off main thread!");
223
0
224
0
  nsAutoCString targetURI, sourceURI;
225
0
  mTargetURI->GetAsciiSpec(targetURI);
226
0
  if (mSourceURI) {
227
0
    mSourceURI->GetAsciiSpec(sourceURI);
228
0
  }
229
0
  PREDICTOR_LOG(("OnCacheEntryAvailable %p called. entry=%p mFullUri=%d mPredict=%d "
230
0
                 "mPredictReason=%d mLearnReason=%d mTargetURI=%s "
231
0
                 "mSourceURI=%s mStackCount=%d isNew=%d result=0x%08" PRIx32,
232
0
                 this, entry, mFullUri, mPredict, mPredictReason, mLearnReason,
233
0
                 targetURI.get(), sourceURI.get(), mStackCount,
234
0
                 isNew, static_cast<uint32_t>(result)));
235
0
  if (NS_FAILED(result)) {
236
0
    PREDICTOR_LOG(("OnCacheEntryAvailable %p FAILED to get cache entry (0x%08" PRIX32
237
0
                   "). Aborting.", this, static_cast<uint32_t>(result)));
238
0
    return NS_OK;
239
0
  }
240
0
  Telemetry::AccumulateTimeDelta(Telemetry::PREDICTOR_WAIT_TIME,
241
0
                                 mStartTime);
242
0
  if (mPredict) {
243
0
    bool predicted = mPredictor->PredictInternal(mPredictReason, entry, isNew,
244
0
                                                 mFullUri, mTargetURI,
245
0
                                                 mVerifier, mStackCount);
246
0
    Telemetry::AccumulateTimeDelta(
247
0
      Telemetry::PREDICTOR_PREDICT_WORK_TIME, mStartTime);
248
0
    if (predicted) {
249
0
      Telemetry::AccumulateTimeDelta(
250
0
        Telemetry::PREDICTOR_PREDICT_TIME_TO_ACTION, mStartTime);
251
0
    } else {
252
0
      Telemetry::AccumulateTimeDelta(
253
0
        Telemetry::PREDICTOR_PREDICT_TIME_TO_INACTION, mStartTime);
254
0
    }
255
0
  } else {
256
0
    mPredictor->LearnInternal(mLearnReason, entry, isNew, mFullUri, mTargetURI,
257
0
                              mSourceURI);
258
0
    Telemetry::AccumulateTimeDelta(
259
0
      Telemetry::PREDICTOR_LEARN_WORK_TIME, mStartTime);
260
0
  }
261
0
262
0
  return NS_OK;
263
0
}
264
265
NS_IMPL_ISUPPORTS(Predictor,
266
                  nsINetworkPredictor,
267
                  nsIObserver,
268
                  nsISpeculativeConnectionOverrider,
269
                  nsIInterfaceRequestor,
270
                  nsICacheEntryMetaDataVisitor,
271
                  nsINetworkPredictorVerifier)
272
273
Predictor::Predictor()
274
  :mInitialized(false)
275
  ,mCleanedUp(false)
276
  ,mStartupTime(0)
277
  ,mLastStartupTime(0)
278
  ,mStartupCount(1)
279
0
{
280
0
  MOZ_ASSERT(!sSelf, "multiple Predictor instances!");
281
0
  sSelf = this;
282
0
}
283
284
Predictor::~Predictor()
285
0
{
286
0
  if (mInitialized)
287
0
    Shutdown();
288
0
289
0
  sSelf = nullptr;
290
0
}
291
292
// Predictor::nsIObserver
293
294
nsresult
295
Predictor::InstallObserver()
296
0
{
297
0
  MOZ_ASSERT(NS_IsMainThread(), "Installing observer off main thread");
298
0
299
0
  nsresult rv = NS_OK;
300
0
  nsCOMPtr<nsIObserverService> obs =
301
0
    mozilla::services::GetObserverService();
302
0
  if (!obs) {
303
0
    return NS_ERROR_NOT_AVAILABLE;
304
0
  }
305
0
306
0
  rv = obs->AddObserver(this, NS_XPCOM_SHUTDOWN_OBSERVER_ID, false);
307
0
  NS_ENSURE_SUCCESS(rv, rv);
308
0
309
0
  mCleanedUp = Preferences::GetBool(PREDICTOR_CLEANED_UP_PREF, false);
310
0
311
0
  if (!mCleanedUp) {
312
0
    NS_NewTimerWithObserver(getter_AddRefs(mCleanupTimer),
313
0
                            this, 60 * 1000, nsITimer::TYPE_ONE_SHOT);
314
0
  }
315
0
316
0
  return rv;
317
0
}
318
319
void
320
Predictor::RemoveObserver()
321
0
{
322
0
  MOZ_ASSERT(NS_IsMainThread(), "Removing observer off main thread");
323
0
324
0
  nsCOMPtr<nsIObserverService> obs =
325
0
    mozilla::services::GetObserverService();
326
0
  if (obs) {
327
0
    obs->RemoveObserver(this, NS_XPCOM_SHUTDOWN_OBSERVER_ID);
328
0
  }
329
0
330
0
  if (mCleanupTimer) {
331
0
    mCleanupTimer->Cancel();
332
0
    mCleanupTimer = nullptr;
333
0
  }
334
0
}
335
336
NS_IMETHODIMP
337
Predictor::Observe(nsISupports *subject, const char *topic,
338
                   const char16_t *data_unicode)
339
0
{
340
0
  nsresult rv = NS_OK;
341
0
  MOZ_ASSERT(NS_IsMainThread(),
342
0
             "Predictor observing something off main thread!");
343
0
344
0
  if (!strcmp(NS_XPCOM_SHUTDOWN_OBSERVER_ID, topic)) {
345
0
    Shutdown();
346
0
  } else if (!strcmp("timer-callback", topic)) {
347
0
    MaybeCleanupOldDBFiles();
348
0
    mCleanupTimer = nullptr;
349
0
  }
350
0
351
0
  return rv;
352
0
}
353
354
// Predictor::nsISpeculativeConnectionOverrider
355
356
NS_IMETHODIMP
357
Predictor::GetIgnoreIdle(bool *ignoreIdle)
358
0
{
359
0
  *ignoreIdle = true;
360
0
  return NS_OK;
361
0
}
362
363
NS_IMETHODIMP
364
Predictor::GetParallelSpeculativeConnectLimit(
365
    uint32_t *parallelSpeculativeConnectLimit)
366
0
{
367
0
  *parallelSpeculativeConnectLimit = 6;
368
0
  return NS_OK;
369
0
}
370
371
NS_IMETHODIMP
372
Predictor::GetIsFromPredictor(bool *isFromPredictor)
373
0
{
374
0
  *isFromPredictor = true;
375
0
  return NS_OK;
376
0
}
377
378
NS_IMETHODIMP
379
Predictor::GetAllow1918(bool *allow1918)
380
0
{
381
0
  *allow1918 = false;
382
0
  return NS_OK;
383
0
}
384
385
// Predictor::nsIInterfaceRequestor
386
387
NS_IMETHODIMP
388
Predictor::GetInterface(const nsIID &iid, void **result)
389
0
{
390
0
  return QueryInterface(iid, result);
391
0
}
392
393
// Predictor::nsICacheEntryMetaDataVisitor
394
395
0
#define SEEN_META_DATA "predictor::seen"
396
0
#define RESOURCE_META_DATA "predictor::resource-count"
397
0
#define META_DATA_PREFIX "predictor::"
398
399
static bool
400
IsURIMetadataElement(const char *key)
401
0
{
402
0
  return StringBeginsWith(nsDependentCString(key),
403
0
                          NS_LITERAL_CSTRING(META_DATA_PREFIX)) &&
404
0
         !NS_LITERAL_CSTRING(SEEN_META_DATA).Equals(key) &&
405
0
         !NS_LITERAL_CSTRING(RESOURCE_META_DATA).Equals(key);
406
0
}
407
408
nsresult
409
Predictor::OnMetaDataElement(const char *asciiKey, const char *asciiValue)
410
0
{
411
0
  MOZ_ASSERT(NS_IsMainThread());
412
0
413
0
  if (!IsURIMetadataElement(asciiKey)) {
414
0
    // This isn't a bit of metadata we care about
415
0
    return NS_OK;
416
0
  }
417
0
418
0
  nsCString key, value;
419
0
  key.AssignASCII(asciiKey);
420
0
  value.AssignASCII(asciiValue);
421
0
  mKeysToOperateOn.AppendElement(key);
422
0
  mValuesToOperateOn.AppendElement(value);
423
0
424
0
  return NS_OK;
425
0
}
426
427
// Predictor::nsINetworkPredictor
428
429
nsresult
430
Predictor::Init()
431
0
{
432
0
  MOZ_DIAGNOSTIC_ASSERT(!IsNeckoChild());
433
0
434
0
  if (!NS_IsMainThread()) {
435
0
    MOZ_ASSERT(false, "Predictor::Init called off the main thread!");
436
0
    return NS_ERROR_UNEXPECTED;
437
0
  }
438
0
439
0
  nsresult rv = NS_OK;
440
0
441
0
  rv = InstallObserver();
442
0
  NS_ENSURE_SUCCESS(rv, rv);
443
0
444
0
  mLastStartupTime = mStartupTime = NOW_IN_SECONDS();
445
0
446
0
  if (!mDNSListener) {
447
0
    mDNSListener = new DNSListener();
448
0
  }
449
0
450
0
  mCacheStorageService =
451
0
    do_GetService("@mozilla.org/netwerk/cache-storage-service;1", &rv);
452
0
  NS_ENSURE_SUCCESS(rv, rv);
453
0
454
0
  mIOService = do_GetService("@mozilla.org/network/io-service;1", &rv);
455
0
  NS_ENSURE_SUCCESS(rv, rv);
456
0
457
0
  rv = NS_NewURI(getter_AddRefs(mStartupURI),
458
0
                 "predictor://startup", nullptr, mIOService);
459
0
  NS_ENSURE_SUCCESS(rv, rv);
460
0
461
0
  mSpeculativeService = do_QueryInterface(mIOService, &rv);
462
0
  NS_ENSURE_SUCCESS(rv, rv);
463
0
464
0
  mDnsService = do_GetService("@mozilla.org/network/dns-service;1", &rv);
465
0
  NS_ENSURE_SUCCESS(rv, rv);
466
0
467
0
  Preferences::AddBoolVarCache(&sEsniEnabled, "network.security.esni.enabled");
468
0
469
0
  mInitialized = true;
470
0
471
0
  return rv;
472
0
}
473
474
namespace {
475
class PredictorThreadShutdownRunner : public Runnable
476
{
477
public:
478
  PredictorThreadShutdownRunner(nsIThread* ioThread, bool success)
479
    : Runnable("net::PredictorThreadShutdownRunner")
480
    , mIOThread(ioThread)
481
    , mSuccess(success)
482
0
  { }
483
0
  ~PredictorThreadShutdownRunner() = default;
484
485
  NS_IMETHOD Run() override
486
0
  {
487
0
    MOZ_ASSERT(NS_IsMainThread(), "Shutting down io thread off main thread!");
488
0
    if (mSuccess) {
489
0
      // This means the cleanup happened. Mark so we don't try in the
490
0
      // future.
491
0
      Preferences::SetBool(PREDICTOR_CLEANED_UP_PREF, true);
492
0
    }
493
0
    return mIOThread->AsyncShutdown();
494
0
  }
495
496
private:
497
  nsCOMPtr<nsIThread> mIOThread;
498
  bool mSuccess;
499
};
500
501
class PredictorOldCleanupRunner : public Runnable
502
{
503
public:
504
  PredictorOldCleanupRunner(nsIThread* ioThread, nsIFile* dbFile)
505
    : Runnable("net::PredictorOldCleanupRunner")
506
    , mIOThread(ioThread)
507
    , mDBFile(dbFile)
508
0
  { }
509
510
0
  ~PredictorOldCleanupRunner() = default;
511
512
  NS_IMETHOD Run() override
513
0
  {
514
0
    MOZ_ASSERT(!NS_IsMainThread(), "Cleaning up old files on main thread!");
515
0
    nsresult rv = CheckForAndDeleteOldDBFiles();
516
0
    RefPtr<PredictorThreadShutdownRunner> runner =
517
0
      new PredictorThreadShutdownRunner(mIOThread, NS_SUCCEEDED(rv));
518
0
    NS_DispatchToMainThread(runner);
519
0
    return NS_OK;
520
0
  }
521
522
private:
523
  nsresult CheckForAndDeleteOldDBFiles()
524
0
  {
525
0
    nsCOMPtr<nsIFile> oldDBFile;
526
0
    nsresult rv = mDBFile->GetParent(getter_AddRefs(oldDBFile));
527
0
    NS_ENSURE_SUCCESS(rv, rv);
528
0
529
0
    rv = oldDBFile->AppendNative(NS_LITERAL_CSTRING("seer.sqlite"));
530
0
    NS_ENSURE_SUCCESS(rv, rv);
531
0
532
0
    bool fileExists = false;
533
0
    rv = oldDBFile->Exists(&fileExists);
534
0
    NS_ENSURE_SUCCESS(rv, rv);
535
0
536
0
    if (fileExists) {
537
0
      rv = oldDBFile->Remove(false);
538
0
      NS_ENSURE_SUCCESS(rv, rv);
539
0
    }
540
0
541
0
    fileExists = false;
542
0
    rv = mDBFile->Exists(&fileExists);
543
0
    NS_ENSURE_SUCCESS(rv, rv);
544
0
545
0
    if (fileExists) {
546
0
      rv = mDBFile->Remove(false);
547
0
    }
548
0
549
0
    return rv;
550
0
  }
551
552
  nsCOMPtr<nsIThread> mIOThread;
553
  nsCOMPtr<nsIFile> mDBFile;
554
};
555
556
class PredictorLearnRunnable final : public Runnable {
557
public:
558
  PredictorLearnRunnable(nsIURI *targetURI, nsIURI *sourceURI,
559
                         PredictorLearnReason reason, const OriginAttributes &oa)
560
    : Runnable("PredictorLearnRunnable")
561
    , mTargetURI(targetURI)
562
    , mSourceURI(sourceURI)
563
    , mReason(reason)
564
    , mOA(oa)
565
0
  { }
566
567
0
  ~PredictorLearnRunnable() = default;
568
569
  NS_IMETHOD Run() override
570
0
  {
571
0
    if (!gNeckoChild) {
572
0
      // This may have gone away between when this runnable was dispatched and
573
0
      // when it actually runs, so let's be safe here, even though we asserted
574
0
      // earlier.
575
0
      PREDICTOR_LOG(("predictor::learn (async) gNeckoChild went away"));
576
0
      return NS_OK;
577
0
    }
578
0
579
0
    ipc::URIParams serTargetURI;
580
0
    SerializeURI(mTargetURI, serTargetURI);
581
0
582
0
    ipc::OptionalURIParams serSourceURI;
583
0
    SerializeURI(mSourceURI, serSourceURI);
584
0
585
0
    PREDICTOR_LOG(("predictor::learn (async) forwarding to parent"));
586
0
    gNeckoChild->SendPredLearn(serTargetURI, serSourceURI, mReason, mOA);
587
0
588
0
    return NS_OK;
589
0
  }
590
591
private:
592
  nsCOMPtr<nsIURI> mTargetURI;
593
  nsCOMPtr<nsIURI> mSourceURI;
594
  PredictorLearnReason mReason;
595
  const OriginAttributes mOA;
596
};
597
598
} // namespace
599
600
void
601
Predictor::MaybeCleanupOldDBFiles()
602
0
{
603
0
  MOZ_ASSERT(NS_IsMainThread());
604
0
605
0
  if (!StaticPrefs::network_predictor_enabled() || mCleanedUp) {
606
0
    return;
607
0
  }
608
0
609
0
  mCleanedUp = true;
610
0
611
0
  // This is used for cleaning up junk left over from the old backend
612
0
  // built on top of sqlite, if necessary.
613
0
  nsCOMPtr<nsIFile> dbFile;
614
0
  nsresult rv = NS_GetSpecialDirectory(NS_APP_USER_PROFILE_50_DIR,
615
0
                                       getter_AddRefs(dbFile));
616
0
  RETURN_IF_FAILED(rv);
617
0
  rv = dbFile->AppendNative(NS_LITERAL_CSTRING("netpredictions.sqlite"));
618
0
  RETURN_IF_FAILED(rv);
619
0
620
0
  nsCOMPtr<nsIThread> ioThread;
621
0
  rv = NS_NewNamedThread("NetPredictClean", getter_AddRefs(ioThread));
622
0
  RETURN_IF_FAILED(rv);
623
0
624
0
  RefPtr<PredictorOldCleanupRunner> runner =
625
0
    new PredictorOldCleanupRunner(ioThread, dbFile);
626
0
  ioThread->Dispatch(runner, NS_DISPATCH_NORMAL);
627
0
}
628
629
void
630
Predictor::Shutdown()
631
0
{
632
0
  if (!NS_IsMainThread()) {
633
0
    MOZ_ASSERT(false, "Predictor::Shutdown called off the main thread!");
634
0
    return;
635
0
  }
636
0
637
0
  RemoveObserver();
638
0
639
0
  mInitialized = false;
640
0
}
641
642
nsresult
643
Predictor::Create(nsISupports *aOuter, const nsIID& aIID,
644
                  void **aResult)
645
0
{
646
0
  MOZ_ASSERT(NS_IsMainThread());
647
0
648
0
  nsresult rv;
649
0
650
0
  if (aOuter != nullptr) {
651
0
    return NS_ERROR_NO_AGGREGATION;
652
0
  }
653
0
654
0
  RefPtr<Predictor> svc = new Predictor();
655
0
  if (IsNeckoChild()) {
656
0
    // Child threads only need to be call into the public interface methods
657
0
    // so we don't bother with initialization
658
0
    return svc->QueryInterface(aIID, aResult);
659
0
  }
660
0
661
0
  rv = svc->Init();
662
0
  if (NS_FAILED(rv)) {
663
0
    PREDICTOR_LOG(("Failed to initialize predictor, predictor will be a noop"));
664
0
  }
665
0
666
0
  // We treat init failure the same as the service being disabled, since this
667
0
  // is all an optimization anyway. No need to freak people out. That's why we
668
0
  // gladly continue on QI'ing here.
669
0
  rv = svc->QueryInterface(aIID, aResult);
670
0
671
0
  return rv;
672
0
}
673
674
NS_IMETHODIMP
675
Predictor::Predict(nsIURI *targetURI, nsIURI *sourceURI,
676
                   PredictorPredictReason reason,
677
                   JS::HandleValue originAttributes,
678
                   nsINetworkPredictorVerifier *verifier,
679
                   JSContext* aCx)
680
0
{
681
0
  OriginAttributes attrs;
682
0
683
0
  if (!originAttributes.isObject() ||
684
0
      !attrs.Init(aCx, originAttributes)) {
685
0
    return NS_ERROR_INVALID_ARG;
686
0
  }
687
0
688
0
  return PredictNative(targetURI, sourceURI, reason, attrs, verifier);
689
0
}
690
691
// Called from the main thread to initiate predictive actions
692
NS_IMETHODIMP
693
Predictor::PredictNative(nsIURI *targetURI, nsIURI *sourceURI,
694
                         PredictorPredictReason reason,
695
                         const OriginAttributes& originAttributes,
696
                         nsINetworkPredictorVerifier *verifier)
697
0
{
698
0
  MOZ_ASSERT(NS_IsMainThread(),
699
0
             "Predictor interface methods must be called on the main thread");
700
0
701
0
  PREDICTOR_LOG(("Predictor::Predict"));
702
0
703
0
  if (IsNeckoChild()) {
704
0
    MOZ_DIAGNOSTIC_ASSERT(gNeckoChild);
705
0
706
0
    PREDICTOR_LOG(("    called on child process"));
707
0
708
0
    ipc::OptionalURIParams serTargetURI, serSourceURI;
709
0
    SerializeURI(targetURI, serTargetURI);
710
0
    SerializeURI(sourceURI, serSourceURI);
711
0
712
0
    // If two different threads are predicting concurently, this will be
713
0
    // overwritten. Thankfully, we only use this in tests, which will
714
0
    // overwrite mVerifier perhaps multiple times for each individual test;
715
0
    // however, within each test, the multiple predict calls should have the
716
0
    // same verifier.
717
0
    if (verifier) {
718
0
      PREDICTOR_LOG(("    was given a verifier"));
719
0
      mChildVerifier = verifier;
720
0
    }
721
0
    PREDICTOR_LOG(("    forwarding to parent process"));
722
0
    gNeckoChild->SendPredPredict(serTargetURI, serSourceURI,
723
0
                                 reason, originAttributes, verifier);
724
0
    return NS_OK;
725
0
  }
726
0
727
0
  PREDICTOR_LOG(("    called on parent process"));
728
0
729
0
  if (!mInitialized) {
730
0
    PREDICTOR_LOG(("    not initialized"));
731
0
    return NS_OK;
732
0
  }
733
0
734
0
  if (!StaticPrefs::network_predictor_enabled()) {
735
0
    PREDICTOR_LOG(("    not enabled"));
736
0
    return NS_OK;
737
0
  }
738
0
739
0
  if (originAttributes.mPrivateBrowsingId > 0) {
740
0
    // Don't want to do anything in PB mode
741
0
    PREDICTOR_LOG(("    in PB mode"));
742
0
    return NS_OK;
743
0
  }
744
0
745
0
  if (!IsNullOrHttp(targetURI) || !IsNullOrHttp(sourceURI)) {
746
0
    // Nothing we can do for non-HTTP[S] schemes
747
0
    PREDICTOR_LOG(("    got non-http[s] URI"));
748
0
    return NS_OK;
749
0
  }
750
0
751
0
  // Ensure we've been given the appropriate arguments for the kind of
752
0
  // prediction we're being asked to do
753
0
  nsCOMPtr<nsIURI> uriKey = targetURI;
754
0
  nsCOMPtr<nsIURI> originKey;
755
0
  switch (reason) {
756
0
    case nsINetworkPredictor::PREDICT_LINK:
757
0
      if (!targetURI || !sourceURI) {
758
0
        PREDICTOR_LOG(("    link invalid URI state"));
759
0
        return NS_ERROR_INVALID_ARG;
760
0
      }
761
0
      // Link hover is a special case where we can predict without hitting the
762
0
      // db, so let's go ahead and fire off that prediction here.
763
0
      PredictForLink(targetURI, sourceURI,
764
0
                     originAttributes, verifier);
765
0
      return NS_OK;
766
0
    case nsINetworkPredictor::PREDICT_LOAD:
767
0
      if (!targetURI || sourceURI) {
768
0
        PREDICTOR_LOG(("    load invalid URI state"));
769
0
        return NS_ERROR_INVALID_ARG;
770
0
      }
771
0
      break;
772
0
    case nsINetworkPredictor::PREDICT_STARTUP:
773
0
      if (targetURI || sourceURI) {
774
0
        PREDICTOR_LOG(("    startup invalid URI state"));
775
0
        return NS_ERROR_INVALID_ARG;
776
0
      }
777
0
      uriKey = mStartupURI;
778
0
      originKey = mStartupURI;
779
0
      break;
780
0
    default:
781
0
      PREDICTOR_LOG(("    invalid reason"));
782
0
      return NS_ERROR_INVALID_ARG;
783
0
  }
784
0
785
0
  Predictor::Reason argReason;
786
0
  argReason.mPredict = reason;
787
0
788
0
  // First we open the regular cache entry, to ensure we don't gum up the works
789
0
  // waiting on the less-important predictor-only cache entry
790
0
  RefPtr<Predictor::Action> uriAction =
791
0
    new Predictor::Action(Predictor::Action::IS_FULL_URI,
792
0
                          Predictor::Action::DO_PREDICT, argReason, targetURI,
793
0
                          nullptr, verifier, this);
794
0
  nsAutoCString uriKeyStr;
795
0
  uriKey->GetAsciiSpec(uriKeyStr);
796
0
  PREDICTOR_LOG(("    Predict uri=%s reason=%d action=%p", uriKeyStr.get(),
797
0
                 reason, uriAction.get()));
798
0
799
0
  nsCOMPtr<nsICacheStorage> cacheDiskStorage;
800
0
801
0
  RefPtr<LoadContextInfo> lci =
802
0
    new LoadContextInfo(false, originAttributes);
803
0
804
0
  nsresult rv = mCacheStorageService->DiskCacheStorage(lci, false,
805
0
                                                       getter_AddRefs(cacheDiskStorage));
806
0
  NS_ENSURE_SUCCESS(rv, rv);
807
0
808
0
809
0
  uint32_t openFlags = nsICacheStorage::OPEN_READONLY |
810
0
                       nsICacheStorage::OPEN_SECRETLY |
811
0
                       nsICacheStorage::OPEN_PRIORITY |
812
0
                       nsICacheStorage::CHECK_MULTITHREADED;
813
0
  cacheDiskStorage->AsyncOpenURI(uriKey, EmptyCString(), openFlags, uriAction);
814
0
815
0
  // Now we do the origin-only (and therefore predictor-only) entry
816
0
  nsCOMPtr<nsIURI> targetOrigin;
817
0
  rv = ExtractOrigin(uriKey, getter_AddRefs(targetOrigin), mIOService);
818
0
  NS_ENSURE_SUCCESS(rv, rv);
819
0
  if (!originKey) {
820
0
    originKey = targetOrigin;
821
0
  }
822
0
823
0
  RefPtr<Predictor::Action> originAction =
824
0
    new Predictor::Action(Predictor::Action::IS_ORIGIN,
825
0
                          Predictor::Action::DO_PREDICT, argReason,
826
0
                          targetOrigin, nullptr, verifier, this);
827
0
  nsAutoCString originKeyStr;
828
0
  originKey->GetAsciiSpec(originKeyStr);
829
0
  PREDICTOR_LOG(("    Predict origin=%s reason=%d action=%p", originKeyStr.get(),
830
0
                 reason, originAction.get()));
831
0
  openFlags = nsICacheStorage::OPEN_READONLY |
832
0
              nsICacheStorage::OPEN_SECRETLY |
833
0
              nsICacheStorage::CHECK_MULTITHREADED;
834
0
  cacheDiskStorage->AsyncOpenURI(originKey,
835
0
                                 NS_LITERAL_CSTRING(PREDICTOR_ORIGIN_EXTENSION),
836
0
                                 openFlags, originAction);
837
0
838
0
  PREDICTOR_LOG(("    predict returning"));
839
0
  return NS_OK;
840
0
}
841
842
bool
843
Predictor::PredictInternal(PredictorPredictReason reason, nsICacheEntry *entry,
844
                           bool isNew, bool fullUri, nsIURI *targetURI,
845
                           nsINetworkPredictorVerifier *verifier,
846
                           uint8_t stackCount)
847
0
{
848
0
  MOZ_ASSERT(NS_IsMainThread());
849
0
850
0
  PREDICTOR_LOG(("Predictor::PredictInternal"));
851
0
  bool rv = false;
852
0
853
0
  nsCOMPtr<nsILoadContextInfo> lci;
854
0
  entry->GetLoadContextInfo(getter_AddRefs(lci));
855
0
856
0
  if (!lci) {
857
0
    return rv;
858
0
  }
859
0
860
0
  if (reason == nsINetworkPredictor::PREDICT_LOAD) {
861
0
    MaybeLearnForStartup(targetURI, fullUri,
862
0
                         *lci->OriginAttributesPtr());
863
0
  }
864
0
865
0
  if (isNew) {
866
0
    // nothing else we can do here
867
0
    PREDICTOR_LOG(("    new entry"));
868
0
    return rv;
869
0
  }
870
0
871
0
  switch (reason) {
872
0
    case nsINetworkPredictor::PREDICT_LOAD:
873
0
      rv = PredictForPageload(entry, targetURI, stackCount, fullUri, verifier);
874
0
      break;
875
0
    case nsINetworkPredictor::PREDICT_STARTUP:
876
0
      rv = PredictForStartup(entry, fullUri, verifier);
877
0
      break;
878
0
    default:
879
0
      PREDICTOR_LOG(("    invalid reason"));
880
0
      MOZ_ASSERT(false, "Got unexpected value for prediction reason");
881
0
  }
882
0
883
0
  return rv;
884
0
}
885
886
void
887
Predictor::PredictForLink(nsIURI *targetURI, nsIURI *sourceURI,
888
                          const OriginAttributes& originAttributes,
889
                          nsINetworkPredictorVerifier *verifier)
890
0
{
891
0
  MOZ_ASSERT(NS_IsMainThread());
892
0
893
0
  PREDICTOR_LOG(("Predictor::PredictForLink"));
894
0
  if (!mSpeculativeService) {
895
0
    PREDICTOR_LOG(("    missing speculative service"));
896
0
    return;
897
0
  }
898
0
899
0
  if (!StaticPrefs::network_predictor_enable_hover_on_ssl()) {
900
0
    bool isSSL = false;
901
0
    sourceURI->SchemeIs("https", &isSSL);
902
0
    if (isSSL) {
903
0
      // We don't want to predict from an HTTPS page, to avoid info leakage
904
0
      PREDICTOR_LOG(("    Not predicting for link hover - on an SSL page"));
905
0
      return;
906
0
    }
907
0
  }
908
0
909
0
  nsCOMPtr<nsIPrincipal> principal =
910
0
    BasePrincipal::CreateCodebasePrincipal(targetURI, originAttributes);
911
0
912
0
  mSpeculativeService->SpeculativeConnect2(targetURI, principal, nullptr);
913
0
  if (verifier) {
914
0
    PREDICTOR_LOG(("    sending verification"));
915
0
    verifier->OnPredictPreconnect(targetURI);
916
0
  }
917
0
}
918
919
// This is the driver for prediction based on a new pageload.
920
static const uint8_t MAX_PAGELOAD_DEPTH = 10;
921
bool
922
Predictor::PredictForPageload(nsICacheEntry *entry, nsIURI *targetURI,
923
                              uint8_t stackCount, bool fullUri,
924
                              nsINetworkPredictorVerifier *verifier)
925
0
{
926
0
  MOZ_ASSERT(NS_IsMainThread());
927
0
928
0
  PREDICTOR_LOG(("Predictor::PredictForPageload"));
929
0
930
0
  if (stackCount > MAX_PAGELOAD_DEPTH) {
931
0
    PREDICTOR_LOG(("    exceeded recursion depth!"));
932
0
    return false;
933
0
  }
934
0
935
0
  uint32_t lastLoad;
936
0
  nsresult rv = entry->GetLastFetched(&lastLoad);
937
0
  NS_ENSURE_SUCCESS(rv, false);
938
0
939
0
  int32_t globalDegradation = CalculateGlobalDegradation(lastLoad);
940
0
  PREDICTOR_LOG(("    globalDegradation = %d", globalDegradation));
941
0
942
0
  int32_t loadCount;
943
0
  rv = entry->GetFetchCount(&loadCount);
944
0
  NS_ENSURE_SUCCESS(rv, false);
945
0
946
0
  nsCOMPtr<nsILoadContextInfo> lci;
947
0
948
0
  rv = entry->GetLoadContextInfo(getter_AddRefs(lci));
949
0
  NS_ENSURE_SUCCESS(rv, false);
950
0
951
0
  nsCOMPtr<nsIURI> redirectURI;
952
0
  if (WouldRedirect(entry, loadCount, lastLoad, globalDegradation,
953
0
                    getter_AddRefs(redirectURI))) {
954
0
    mPreconnects.AppendElement(redirectURI);
955
0
    Predictor::Reason reason;
956
0
    reason.mPredict = nsINetworkPredictor::PREDICT_LOAD;
957
0
    RefPtr<Predictor::Action> redirectAction =
958
0
      new Predictor::Action(Predictor::Action::IS_FULL_URI,
959
0
                            Predictor::Action::DO_PREDICT, reason, redirectURI,
960
0
                            nullptr, verifier, this, stackCount + 1);
961
0
    nsAutoCString redirectUriString;
962
0
    redirectURI->GetAsciiSpec(redirectUriString);
963
0
964
0
    nsCOMPtr<nsICacheStorage> cacheDiskStorage;
965
0
966
0
    rv = mCacheStorageService->DiskCacheStorage(lci, false,
967
0
                                               getter_AddRefs(cacheDiskStorage));
968
0
    NS_ENSURE_SUCCESS(rv, false);
969
0
970
0
    PREDICTOR_LOG(("    Predict redirect uri=%s action=%p", redirectUriString.get(),
971
0
                   redirectAction.get()));
972
0
    uint32_t openFlags = nsICacheStorage::OPEN_READONLY |
973
0
                         nsICacheStorage::OPEN_SECRETLY |
974
0
                         nsICacheStorage::OPEN_PRIORITY |
975
0
                         nsICacheStorage::CHECK_MULTITHREADED;
976
0
    cacheDiskStorage->AsyncOpenURI(redirectURI, EmptyCString(), openFlags,
977
0
                                    redirectAction);
978
0
    return RunPredictions(nullptr, *lci->OriginAttributesPtr(), verifier);
979
0
  }
980
0
981
0
  CalculatePredictions(entry, targetURI, lastLoad, loadCount, globalDegradation, fullUri);
982
0
983
0
  return RunPredictions(targetURI, *lci->OriginAttributesPtr(), verifier);
984
0
}
985
986
// This is the driver for predicting at browser startup time based on pages that
987
// have previously been loaded close to startup.
988
bool
989
Predictor::PredictForStartup(nsICacheEntry *entry, bool fullUri,
990
                             nsINetworkPredictorVerifier *verifier)
991
0
{
992
0
  MOZ_ASSERT(NS_IsMainThread());
993
0
994
0
  PREDICTOR_LOG(("Predictor::PredictForStartup"));
995
0
996
0
  nsCOMPtr<nsILoadContextInfo> lci;
997
0
998
0
  nsresult rv = entry->GetLoadContextInfo(getter_AddRefs(lci));
999
0
  NS_ENSURE_SUCCESS(rv, false);
1000
0
1001
0
  int32_t globalDegradation = CalculateGlobalDegradation(mLastStartupTime);
1002
0
  CalculatePredictions(entry, nullptr, mLastStartupTime, mStartupCount,
1003
0
                       globalDegradation, fullUri);
1004
0
  return RunPredictions(nullptr, *lci->OriginAttributesPtr(), verifier);
1005
0
}
1006
1007
// This calculates how much to degrade our confidence in our data based on
1008
// the last time this top-level resource was loaded. This "global degradation"
1009
// applies to *all* subresources we have associated with the top-level
1010
// resource. This will be in addition to any reduction in confidence we have
1011
// associated with a particular subresource.
1012
int32_t
1013
Predictor::CalculateGlobalDegradation(uint32_t lastLoad)
1014
0
{
1015
0
  MOZ_ASSERT(NS_IsMainThread());
1016
0
1017
0
  int32_t globalDegradation;
1018
0
  uint32_t delta = NOW_IN_SECONDS() - lastLoad;
1019
0
  if (delta < ONE_DAY) {
1020
0
    globalDegradation =
1021
0
      StaticPrefs::network_predictor_page_degradation_day();
1022
0
  } else if (delta < ONE_WEEK) {
1023
0
    globalDegradation =
1024
0
      StaticPrefs::network_predictor_page_degradation_week();
1025
0
  } else if (delta < ONE_MONTH) {
1026
0
    globalDegradation =
1027
0
      StaticPrefs::network_predictor_page_degradation_month();
1028
0
  } else if (delta < ONE_YEAR) {
1029
0
    globalDegradation =
1030
0
      StaticPrefs::network_predictor_page_degradation_year();
1031
0
  } else {
1032
0
    globalDegradation =
1033
0
      StaticPrefs::network_predictor_page_degradation_max();
1034
0
  }
1035
0
1036
0
  Telemetry::Accumulate(Telemetry::PREDICTOR_GLOBAL_DEGRADATION,
1037
0
                        globalDegradation);
1038
0
  return globalDegradation;
1039
0
}
1040
1041
// This calculates our overall confidence that a particular subresource will be
1042
// loaded as part of a top-level load.
1043
// @param hitCount - the number of times we have loaded this subresource as part
1044
//                   of this top-level load
1045
// @param hitsPossible - the number of times we have performed this top-level
1046
//                       load
1047
// @param lastHit - the timestamp of the last time we loaded this subresource as
1048
//                  part of this top-level load
1049
// @param lastPossible - the timestamp of the last time we performed this
1050
//                       top-level load
1051
// @param globalDegradation - the degradation for this top-level load as
1052
//                            determined by CalculateGlobalDegradation
1053
int32_t
1054
Predictor::CalculateConfidence(uint32_t hitCount, uint32_t hitsPossible,
1055
                               uint32_t lastHit, uint32_t lastPossible,
1056
                               int32_t globalDegradation)
1057
0
{
1058
0
  MOZ_ASSERT(NS_IsMainThread());
1059
0
1060
0
  Telemetry::AutoCounter<Telemetry::PREDICTOR_PREDICTIONS_CALCULATED> predictionsCalculated;
1061
0
  ++predictionsCalculated;
1062
0
1063
0
  if (!hitsPossible) {
1064
0
    return 0;
1065
0
  }
1066
0
1067
0
  int32_t baseConfidence = (hitCount * 100) / hitsPossible;
1068
0
  int32_t maxConfidence = 100;
1069
0
  int32_t confidenceDegradation = 0;
1070
0
1071
0
  if (lastHit < lastPossible) {
1072
0
    // We didn't load this subresource the last time this top-level load was
1073
0
    // performed, so let's not bother preconnecting (at the very least).
1074
0
    maxConfidence =
1075
0
      StaticPrefs::network_predictor_preconnect_min_confidence() - 1;
1076
0
1077
0
    // Now calculate how much we want to degrade our confidence based on how
1078
0
    // long it's been between the last time we did this top-level load and the
1079
0
    // last time this top-level load included this subresource.
1080
0
    PRTime delta = lastPossible - lastHit;
1081
0
    if (delta == 0) {
1082
0
      confidenceDegradation = 0;
1083
0
    } else if (delta < ONE_DAY) {
1084
0
      confidenceDegradation =
1085
0
        StaticPrefs::network_predictor_subresource_degradation_day();
1086
0
    } else if (delta < ONE_WEEK) {
1087
0
      confidenceDegradation =
1088
0
        StaticPrefs::network_predictor_subresource_degradation_week();
1089
0
    } else if (delta < ONE_MONTH) {
1090
0
      confidenceDegradation =
1091
0
        StaticPrefs::network_predictor_subresource_degradation_month();
1092
0
    } else if (delta < ONE_YEAR) {
1093
0
      confidenceDegradation =
1094
0
        StaticPrefs::network_predictor_subresource_degradation_year();
1095
0
    } else {
1096
0
      confidenceDegradation =
1097
0
        StaticPrefs::network_predictor_subresource_degradation_max();
1098
0
      maxConfidence = 0;
1099
0
    }
1100
0
  }
1101
0
1102
0
  // Calculate our confidence and clamp it to between 0 and maxConfidence
1103
0
  // (<= 100)
1104
0
  int32_t confidence = baseConfidence - confidenceDegradation - globalDegradation;
1105
0
  confidence = std::max(confidence, 0);
1106
0
  confidence = std::min(confidence, maxConfidence);
1107
0
1108
0
  Telemetry::Accumulate(Telemetry::PREDICTOR_BASE_CONFIDENCE, baseConfidence);
1109
0
  Telemetry::Accumulate(Telemetry::PREDICTOR_SUBRESOURCE_DEGRADATION,
1110
0
                        confidenceDegradation);
1111
0
  Telemetry::Accumulate(Telemetry::PREDICTOR_CONFIDENCE, confidence);
1112
0
  return confidence;
1113
0
}
1114
1115
static void
1116
MakeMetadataEntry(const uint32_t hitCount, const uint32_t lastHit,
1117
                  const uint32_t flags, nsCString &newValue)
1118
0
{
1119
0
  newValue.Truncate();
1120
0
  newValue.AppendInt(METADATA_VERSION);
1121
0
  newValue.Append(',');
1122
0
  newValue.AppendInt(hitCount);
1123
0
  newValue.Append(',');
1124
0
  newValue.AppendInt(lastHit);
1125
0
  newValue.Append(',');
1126
0
  newValue.AppendInt(flags);
1127
0
}
1128
1129
// On every page load, the rolling window gets shifted by one bit, leaving the
1130
// lowest bit at 0, to indicate that the subresource in question has not been
1131
// seen on the most recent page load. If, at some point later during the page load,
1132
// the subresource is seen again, we will then set the lowest bit to 1. This is
1133
// how we keep track of how many of the last n pageloads (for n <= 20) a particular
1134
// subresource has been seen.
1135
// The rolling window is kept in the upper 20 bits of the flags element of the
1136
// metadata. This saves 12 bits for regular old flags.
1137
void
1138
Predictor::UpdateRollingLoadCount(nsICacheEntry *entry, const uint32_t flags,
1139
                                  const char *key, const uint32_t hitCount,
1140
                                  const uint32_t lastHit)
1141
0
{
1142
0
  // Extract just the rolling load count from the flags, shift it to clear the
1143
0
  // lowest bit, and put the new value with the existing flags.
1144
0
  uint32_t rollingLoadCount = flags & ~kFlagsMask;
1145
0
  rollingLoadCount <<= 1;
1146
0
  uint32_t newFlags = (flags & kFlagsMask) | rollingLoadCount;
1147
0
1148
0
  // Finally, update the metadata on the cache entry.
1149
0
  nsAutoCString newValue;
1150
0
  MakeMetadataEntry(hitCount, lastHit, newFlags, newValue);
1151
0
  entry->SetMetaDataElement(key, newValue.BeginReading());
1152
0
}
1153
1154
uint32_t
1155
Predictor::ClampedPrefetchRollingLoadCount()
1156
0
{
1157
0
  int32_t n = StaticPrefs::network_predictor_prefetch_rolling_load_count();
1158
0
  if (n < 0) {
1159
0
    return 0;
1160
0
  }
1161
0
  if (n > kMaxPrefetchRollingLoadCount) {
1162
0
    return kMaxPrefetchRollingLoadCount;
1163
0
  }
1164
0
  return n;
1165
0
}
1166
1167
void
1168
Predictor::CalculatePredictions(nsICacheEntry *entry, nsIURI *referrer,
1169
                                uint32_t lastLoad, uint32_t loadCount,
1170
                                int32_t globalDegradation, bool fullUri)
1171
0
{
1172
0
  MOZ_ASSERT(NS_IsMainThread());
1173
0
1174
0
  // Since the visitor gets called under a cache lock, all we do there is get
1175
0
  // copies of the keys/values we care about, and then do the real work here
1176
0
  entry->VisitMetaData(this);
1177
0
  nsTArray<nsCString> keysToOperateOn, valuesToOperateOn;
1178
0
  keysToOperateOn.SwapElements(mKeysToOperateOn);
1179
0
  valuesToOperateOn.SwapElements(mValuesToOperateOn);
1180
0
1181
0
  MOZ_ASSERT(keysToOperateOn.Length() == valuesToOperateOn.Length());
1182
0
  for (size_t i = 0; i < keysToOperateOn.Length(); ++i) {
1183
0
    const char *key = keysToOperateOn[i].BeginReading();
1184
0
    const char *value = valuesToOperateOn[i].BeginReading();
1185
0
1186
0
    nsCString uri;
1187
0
    uint32_t hitCount, lastHit, flags;
1188
0
    if (!ParseMetaDataEntry(key, value, uri, hitCount, lastHit, flags)) {
1189
0
      // This failed, get rid of it so we don't waste space
1190
0
      entry->SetMetaDataElement(key, nullptr);
1191
0
      continue;
1192
0
    }
1193
0
1194
0
    int32_t confidence = CalculateConfidence(hitCount, loadCount, lastHit,
1195
0
                                             lastLoad, globalDegradation);
1196
0
    if (fullUri) {
1197
0
      UpdateRollingLoadCount(entry, flags, key, hitCount, lastHit);
1198
0
    }
1199
0
    PREDICTOR_LOG(("CalculatePredictions key=%s value=%s confidence=%d", key, value, confidence));
1200
0
    PrefetchIgnoreReason reason = PREFETCH_OK;
1201
0
    if (!fullUri) {
1202
0
      // Not full URI - don't prefetch! No sense in it!
1203
0
      PREDICTOR_LOG(("    forcing non-cacheability - not full URI"));
1204
0
      if (flags & FLAG_PREFETCHABLE) {
1205
0
        // This only applies if we had somehow otherwise marked this
1206
0
        // prefetchable.
1207
0
        reason = NOT_FULL_URI;
1208
0
      }
1209
0
      flags &= ~FLAG_PREFETCHABLE;
1210
0
    } else if (!referrer) {
1211
0
      // No referrer means we can't prefetch, so pretend it's non-cacheable,
1212
0
      // no matter what.
1213
0
      PREDICTOR_LOG(("    forcing non-cacheability - no referrer"));
1214
0
      if (flags & FLAG_PREFETCHABLE) {
1215
0
        // This only applies if we had somehow otherwise marked this
1216
0
        // prefetchable.
1217
0
        reason = NO_REFERRER;
1218
0
      }
1219
0
      flags &= ~FLAG_PREFETCHABLE;
1220
0
    } else {
1221
0
      uint32_t expectedRollingLoadCount =
1222
0
        (1 << ClampedPrefetchRollingLoadCount()) - 1;
1223
0
      expectedRollingLoadCount <<= kRollingLoadOffset;
1224
0
      if ((flags & expectedRollingLoadCount) != expectedRollingLoadCount) {
1225
0
        PREDICTOR_LOG(("    forcing non-cacheability - missed a load"));
1226
0
        if (flags & FLAG_PREFETCHABLE) {
1227
0
          // This only applies if we had somehow otherwise marked this
1228
0
          // prefetchable.
1229
0
          reason = MISSED_A_LOAD;
1230
0
        }
1231
0
        flags &= ~FLAG_PREFETCHABLE;
1232
0
      }
1233
0
    }
1234
0
1235
0
    PREDICTOR_LOG(("    setting up prediction"));
1236
0
    SetupPrediction(confidence, flags, uri, reason);
1237
0
  }
1238
0
}
1239
1240
// (Maybe) adds a predictive action to the prediction runner, based on our
1241
// calculated confidence for the subresource in question.
1242
void
1243
Predictor::SetupPrediction(int32_t confidence, uint32_t flags,
1244
                           const nsCString &uri,
1245
                           PrefetchIgnoreReason earlyReason)
1246
0
{
1247
0
  MOZ_ASSERT(NS_IsMainThread());
1248
0
1249
0
  nsresult rv = NS_OK;
1250
0
  PREDICTOR_LOG(("SetupPrediction enable-prefetch=%d prefetch-min-confidence=%d "
1251
0
                 "preconnect-min-confidence=%d preresolve-min-confidence=%d "
1252
0
                 "flags=%d confidence=%d uri=%s",
1253
0
                 StaticPrefs::network_predictor_enable_prefetch(),
1254
0
                 StaticPrefs::network_predictor_prefetch_min_confidence(),
1255
0
                 StaticPrefs::network_predictor_preconnect_min_confidence(),
1256
0
                 StaticPrefs::network_predictor_preresolve_min_confidence(),
1257
0
                 flags, confidence, uri.get()));
1258
0
1259
0
  bool prefetchOk = !!(flags & FLAG_PREFETCHABLE);
1260
0
  PrefetchIgnoreReason reason = earlyReason;
1261
0
  if (prefetchOk && !StaticPrefs::network_predictor_enable_prefetch()) {
1262
0
    prefetchOk = false;
1263
0
    reason = PREFETCH_DISABLED;
1264
0
  } else if (prefetchOk && !ClampedPrefetchRollingLoadCount() &&
1265
0
             confidence < StaticPrefs::network_predictor_prefetch_min_confidence()) {
1266
0
    prefetchOk = false;
1267
0
    if (!ClampedPrefetchRollingLoadCount()) {
1268
0
      reason = PREFETCH_DISABLED_VIA_COUNT;
1269
0
    } else {
1270
0
      reason = CONFIDENCE_TOO_LOW;
1271
0
    }
1272
0
  }
1273
0
1274
0
  // prefetchOk == false and reason == PREFETCH_OK indicates that the reason
1275
0
  // we aren't prefetching this item is because it was marked un-prefetchable in
1276
0
  // our metadata. We already have separate telemetry on that decision, so we
1277
0
  // aren't going to accumulate more here. Right now we only care about why
1278
0
  // something we had marked prefetchable isn't being prefetched.
1279
0
  if (!prefetchOk && reason != PREFETCH_OK) {
1280
0
    Telemetry::Accumulate(Telemetry::PREDICTOR_PREFETCH_IGNORE_REASON, reason);
1281
0
  }
1282
0
1283
0
  if (prefetchOk) {
1284
0
    nsCOMPtr<nsIURI> prefetchURI;
1285
0
    rv = NS_NewURI(getter_AddRefs(prefetchURI), uri, nullptr, nullptr,
1286
0
                   mIOService);
1287
0
    if (NS_SUCCEEDED(rv)) {
1288
0
      mPrefetches.AppendElement(prefetchURI);
1289
0
    }
1290
0
  } else if (confidence >=
1291
0
             StaticPrefs::network_predictor_preconnect_min_confidence()) {
1292
0
    nsCOMPtr<nsIURI> preconnectURI;
1293
0
    rv = NS_NewURI(getter_AddRefs(preconnectURI), uri, nullptr, nullptr,
1294
0
                   mIOService);
1295
0
    if (NS_SUCCEEDED(rv)) {
1296
0
      mPreconnects.AppendElement(preconnectURI);
1297
0
    }
1298
0
  } else if (confidence >=
1299
0
             StaticPrefs::network_predictor_preresolve_min_confidence()) {
1300
0
    nsCOMPtr<nsIURI> preresolveURI;
1301
0
    rv = NS_NewURI(getter_AddRefs(preresolveURI), uri, nullptr, nullptr,
1302
0
                   mIOService);
1303
0
    if (NS_SUCCEEDED(rv)) {
1304
0
      mPreresolves.AppendElement(preresolveURI);
1305
0
    }
1306
0
  }
1307
0
1308
0
  if (NS_FAILED(rv)) {
1309
0
    PREDICTOR_LOG(("    NS_NewURI returned 0x%" PRIx32, static_cast<uint32_t>(rv)));
1310
0
  }
1311
0
}
1312
1313
nsresult
1314
Predictor::Prefetch(nsIURI *uri, nsIURI *referrer,
1315
                    const OriginAttributes& originAttributes,
1316
                    nsINetworkPredictorVerifier *verifier)
1317
0
{
1318
0
  nsAutoCString strUri, strReferrer;
1319
0
  uri->GetAsciiSpec(strUri);
1320
0
  referrer->GetAsciiSpec(strReferrer);
1321
0
  PREDICTOR_LOG(("Predictor::Prefetch uri=%s referrer=%s verifier=%p",
1322
0
                 strUri.get(), strReferrer.get(), verifier));
1323
0
  nsCOMPtr<nsIChannel> channel;
1324
0
  nsresult rv = NS_NewChannel(getter_AddRefs(channel), uri,
1325
0
                              nsContentUtils::GetSystemPrincipal(),
1326
0
                              nsILoadInfo::SEC_ALLOW_CROSS_ORIGIN_DATA_IS_NULL,
1327
0
                              nsIContentPolicy::TYPE_OTHER,
1328
0
                              nullptr, /* aPerformanceStorage */
1329
0
                              nullptr, /* aLoadGroup */
1330
0
                              nullptr, /* aCallbacks */
1331
0
                              nsIRequest::LOAD_BACKGROUND);
1332
0
1333
0
  if (NS_FAILED(rv)) {
1334
0
    PREDICTOR_LOG(("    NS_NewChannel failed rv=0x%" PRIX32, static_cast<uint32_t>(rv)));
1335
0
    return rv;
1336
0
  }
1337
0
1338
0
  nsCOMPtr<nsILoadInfo> loadInfo = channel->GetLoadInfo();
1339
0
  if (loadInfo) {
1340
0
    rv = loadInfo->SetOriginAttributes(originAttributes);
1341
0
  }
1342
0
1343
0
  if (NS_FAILED(rv)) {
1344
0
    PREDICTOR_LOG(("    Set originAttributes into loadInfo failed rv=0x%" PRIX32,
1345
0
                   static_cast<uint32_t>(rv)));
1346
0
    return rv;
1347
0
  }
1348
0
1349
0
  nsCOMPtr<nsIHttpChannel> httpChannel;
1350
0
  httpChannel = do_QueryInterface(channel);
1351
0
  if (!httpChannel) {
1352
0
    PREDICTOR_LOG(("    Could not get HTTP Channel from new channel!"));
1353
0
    return NS_ERROR_UNEXPECTED;
1354
0
  }
1355
0
1356
0
  rv = httpChannel->SetReferrer(referrer);
1357
0
  NS_ENSURE_SUCCESS(rv, rv);
1358
0
  // XXX - set a header here to indicate this is a prefetch?
1359
0
1360
0
  nsCOMPtr<nsIStreamListener> listener = new PrefetchListener(verifier, uri,
1361
0
                                                              this);
1362
0
  PREDICTOR_LOG(("    calling AsyncOpen2 listener=%p channel=%p", listener.get(),
1363
0
                 channel.get()));
1364
0
  rv = channel->AsyncOpen2(listener);
1365
0
  if (NS_FAILED(rv)) {
1366
0
    PREDICTOR_LOG(("    AsyncOpen2 failed rv=0x%" PRIX32, static_cast<uint32_t>(rv)));
1367
0
  }
1368
0
1369
0
  return rv;
1370
0
}
1371
1372
// Runs predictions that have been set up.
1373
bool
1374
Predictor::RunPredictions(nsIURI *referrer,
1375
                          const OriginAttributes& originAttributes,
1376
                          nsINetworkPredictorVerifier *verifier)
1377
0
{
1378
0
  MOZ_ASSERT(NS_IsMainThread(), "Running prediction off main thread");
1379
0
1380
0
  PREDICTOR_LOG(("Predictor::RunPredictions"));
1381
0
1382
0
  bool predicted = false;
1383
0
  uint32_t len, i;
1384
0
1385
0
  nsTArray<nsCOMPtr<nsIURI>> prefetches, preconnects, preresolves;
1386
0
  prefetches.SwapElements(mPrefetches);
1387
0
  preconnects.SwapElements(mPreconnects);
1388
0
  preresolves.SwapElements(mPreresolves);
1389
0
1390
0
  Telemetry::AutoCounter<Telemetry::PREDICTOR_TOTAL_PREDICTIONS> totalPredictions;
1391
0
  Telemetry::AutoCounter<Telemetry::PREDICTOR_TOTAL_PREFETCHES> totalPrefetches;
1392
0
  Telemetry::AutoCounter<Telemetry::PREDICTOR_TOTAL_PRECONNECTS> totalPreconnects;
1393
0
  Telemetry::AutoCounter<Telemetry::PREDICTOR_TOTAL_PRERESOLVES> totalPreresolves;
1394
0
1395
0
  len = prefetches.Length();
1396
0
  for (i = 0; i < len; ++i) {
1397
0
    PREDICTOR_LOG(("    doing prefetch"));
1398
0
    nsCOMPtr<nsIURI> uri = prefetches[i];
1399
0
    if (NS_SUCCEEDED(Prefetch(uri, referrer,
1400
0
                              originAttributes, verifier))) {
1401
0
      ++totalPredictions;
1402
0
      ++totalPrefetches;
1403
0
      predicted = true;
1404
0
    }
1405
0
  }
1406
0
1407
0
  len = preconnects.Length();
1408
0
  for (i = 0; i < len; ++i) {
1409
0
    PREDICTOR_LOG(("    doing preconnect"));
1410
0
    nsCOMPtr<nsIURI> uri = preconnects[i];
1411
0
    ++totalPredictions;
1412
0
    ++totalPreconnects;
1413
0
    nsCOMPtr<nsIPrincipal> principal =
1414
0
      BasePrincipal::CreateCodebasePrincipal(uri, originAttributes);
1415
0
    mSpeculativeService->SpeculativeConnect2(uri, principal, this);
1416
0
    predicted = true;
1417
0
    if (verifier) {
1418
0
      PREDICTOR_LOG(("    sending preconnect verification"));
1419
0
      verifier->OnPredictPreconnect(uri);
1420
0
    }
1421
0
  }
1422
0
1423
0
  len = preresolves.Length();
1424
0
  for (i = 0; i < len; ++i) {
1425
0
    nsCOMPtr<nsIURI> uri = preresolves[i];
1426
0
    ++totalPredictions;
1427
0
    ++totalPreresolves;
1428
0
    nsAutoCString hostname;
1429
0
    uri->GetAsciiHost(hostname);
1430
0
    PREDICTOR_LOG(("    doing preresolve %s", hostname.get()));
1431
0
    nsCOMPtr<nsICancelable> tmpCancelable;
1432
0
    mDnsService->AsyncResolveNative(hostname,
1433
0
                                    (nsIDNSService::RESOLVE_PRIORITY_MEDIUM |
1434
0
                                     nsIDNSService::RESOLVE_SPECULATE),
1435
0
                                    mDNSListener, nullptr, originAttributes,
1436
0
                                    getter_AddRefs(tmpCancelable));
1437
0
1438
0
    bool isHttps;
1439
0
    uri->SchemeIs("https", &isHttps);
1440
0
    // Fetch esni keys if needed.
1441
0
    if (sEsniEnabled && isHttps) {
1442
0
      nsAutoCString esniHost;
1443
0
      esniHost.Append("_esni.");
1444
0
      esniHost.Append(hostname);
1445
0
      mDnsService->AsyncResolveByTypeNative(esniHost,
1446
0
                                            nsIDNSService::RESOLVE_TYPE_TXT,
1447
0
                                            (nsIDNSService::RESOLVE_PRIORITY_MEDIUM |
1448
0
                                             nsIDNSService::RESOLVE_SPECULATE),
1449
0
                                            mDNSListener, nullptr, originAttributes,
1450
0
                                            getter_AddRefs(tmpCancelable));
1451
0
    }
1452
0
1453
0
    predicted = true;
1454
0
    if (verifier) {
1455
0
      PREDICTOR_LOG(("    sending preresolve verification"));
1456
0
      verifier->OnPredictDNS(uri);
1457
0
    }
1458
0
  }
1459
0
1460
0
  return predicted;
1461
0
}
1462
1463
// Find out if a top-level page is likely to redirect.
1464
bool
1465
Predictor::WouldRedirect(nsICacheEntry *entry, uint32_t loadCount,
1466
                         uint32_t lastLoad, int32_t globalDegradation,
1467
                         nsIURI **redirectURI)
1468
0
{
1469
0
  // TODO - not doing redirects for first go around
1470
0
  MOZ_ASSERT(NS_IsMainThread());
1471
0
1472
0
  return false;
1473
0
}
1474
1475
NS_IMETHODIMP
1476
Predictor::Learn(nsIURI *targetURI, nsIURI *sourceURI,
1477
                 PredictorLearnReason reason,
1478
                 JS::HandleValue originAttributes,
1479
                 JSContext* aCx)
1480
0
{
1481
0
  OriginAttributes attrs;
1482
0
1483
0
  if (!originAttributes.isObject() ||
1484
0
      !attrs.Init(aCx, originAttributes)) {
1485
0
    return NS_ERROR_INVALID_ARG;
1486
0
  }
1487
0
1488
0
  return LearnNative(targetURI, sourceURI, reason, attrs);
1489
0
}
1490
1491
// Called from the main thread to update the database
1492
NS_IMETHODIMP
1493
Predictor::LearnNative(nsIURI *targetURI, nsIURI *sourceURI,
1494
                       PredictorLearnReason reason,
1495
                       const OriginAttributes& originAttributes)
1496
0
{
1497
0
  MOZ_ASSERT(NS_IsMainThread(),
1498
0
             "Predictor interface methods must be called on the main thread");
1499
0
1500
0
  PREDICTOR_LOG(("Predictor::Learn"));
1501
0
1502
0
  if (IsNeckoChild()) {
1503
0
    MOZ_DIAGNOSTIC_ASSERT(gNeckoChild);
1504
0
1505
0
    PREDICTOR_LOG(("    called on child process"));
1506
0
1507
0
    RefPtr<PredictorLearnRunnable> runnable = new PredictorLearnRunnable(
1508
0
      targetURI, sourceURI, reason, originAttributes);
1509
0
    SystemGroup::Dispatch(TaskCategory::Other, runnable.forget());
1510
0
1511
0
    return NS_OK;
1512
0
  }
1513
0
1514
0
  PREDICTOR_LOG(("    called on parent process"));
1515
0
1516
0
  if (!mInitialized) {
1517
0
    PREDICTOR_LOG(("    not initialized"));
1518
0
    return NS_OK;
1519
0
  }
1520
0
1521
0
  if (!StaticPrefs::network_predictor_enabled()) {
1522
0
    PREDICTOR_LOG(("    not enabled"));
1523
0
    return NS_OK;
1524
0
  }
1525
0
1526
0
  if (originAttributes.mPrivateBrowsingId > 0) {
1527
0
    // Don't want to do anything in PB mode
1528
0
    PREDICTOR_LOG(("    in PB mode"));
1529
0
    return NS_OK;
1530
0
  }
1531
0
1532
0
  if (!IsNullOrHttp(targetURI) || !IsNullOrHttp(sourceURI)) {
1533
0
    PREDICTOR_LOG(("    got non-HTTP[S] URI"));
1534
0
    return NS_ERROR_INVALID_ARG;
1535
0
  }
1536
0
1537
0
  nsCOMPtr<nsIURI> targetOrigin;
1538
0
  nsCOMPtr<nsIURI> sourceOrigin;
1539
0
  nsCOMPtr<nsIURI> uriKey;
1540
0
  nsCOMPtr<nsIURI> originKey;
1541
0
  nsresult rv;
1542
0
1543
0
  switch (reason) {
1544
0
  case nsINetworkPredictor::LEARN_LOAD_TOPLEVEL:
1545
0
    if (!targetURI || sourceURI) {
1546
0
      PREDICTOR_LOG(("    load toplevel invalid URI state"));
1547
0
      return NS_ERROR_INVALID_ARG;
1548
0
    }
1549
0
    rv = ExtractOrigin(targetURI, getter_AddRefs(targetOrigin), mIOService);
1550
0
    NS_ENSURE_SUCCESS(rv, rv);
1551
0
    uriKey = targetURI;
1552
0
    originKey = targetOrigin;
1553
0
    break;
1554
0
  case nsINetworkPredictor::LEARN_STARTUP:
1555
0
    if (!targetURI || sourceURI) {
1556
0
      PREDICTOR_LOG(("    startup invalid URI state"));
1557
0
      return NS_ERROR_INVALID_ARG;
1558
0
    }
1559
0
    rv = ExtractOrigin(targetURI, getter_AddRefs(targetOrigin), mIOService);
1560
0
    NS_ENSURE_SUCCESS(rv, rv);
1561
0
    uriKey = mStartupURI;
1562
0
    originKey = mStartupURI;
1563
0
    break;
1564
0
  case nsINetworkPredictor::LEARN_LOAD_REDIRECT:
1565
0
  case nsINetworkPredictor::LEARN_LOAD_SUBRESOURCE:
1566
0
    if (!targetURI || !sourceURI) {
1567
0
      PREDICTOR_LOG(("    redirect/subresource invalid URI state"));
1568
0
      return NS_ERROR_INVALID_ARG;
1569
0
    }
1570
0
    rv = ExtractOrigin(targetURI, getter_AddRefs(targetOrigin), mIOService);
1571
0
    NS_ENSURE_SUCCESS(rv, rv);
1572
0
    rv = ExtractOrigin(sourceURI, getter_AddRefs(sourceOrigin), mIOService);
1573
0
    NS_ENSURE_SUCCESS(rv, rv);
1574
0
    uriKey = sourceURI;
1575
0
    originKey = sourceOrigin;
1576
0
    break;
1577
0
  default:
1578
0
    PREDICTOR_LOG(("    invalid reason"));
1579
0
    return NS_ERROR_INVALID_ARG;
1580
0
  }
1581
0
1582
0
  Telemetry::AutoCounter<Telemetry::PREDICTOR_LEARN_ATTEMPTS> learnAttempts;
1583
0
  ++learnAttempts;
1584
0
1585
0
  Predictor::Reason argReason;
1586
0
  argReason.mLearn = reason;
1587
0
1588
0
  // We always open the full uri (general cache) entry first, so we don't gum up
1589
0
  // the works waiting on predictor-only entries to open
1590
0
  RefPtr<Predictor::Action> uriAction =
1591
0
    new Predictor::Action(Predictor::Action::IS_FULL_URI,
1592
0
                          Predictor::Action::DO_LEARN, argReason, targetURI,
1593
0
                          sourceURI, nullptr, this);
1594
0
  nsAutoCString uriKeyStr, targetUriStr, sourceUriStr;
1595
0
  uriKey->GetAsciiSpec(uriKeyStr);
1596
0
  targetURI->GetAsciiSpec(targetUriStr);
1597
0
  if (sourceURI) {
1598
0
    sourceURI->GetAsciiSpec(sourceUriStr);
1599
0
  }
1600
0
  PREDICTOR_LOG(("    Learn uriKey=%s targetURI=%s sourceURI=%s reason=%d "
1601
0
                 "action=%p", uriKeyStr.get(), targetUriStr.get(),
1602
0
                 sourceUriStr.get(), reason, uriAction.get()));
1603
0
1604
0
  nsCOMPtr<nsICacheStorage> cacheDiskStorage;
1605
0
1606
0
  RefPtr<LoadContextInfo> lci =
1607
0
    new LoadContextInfo(false, originAttributes);
1608
0
1609
0
  rv = mCacheStorageService->DiskCacheStorage(lci, false,
1610
0
                                              getter_AddRefs(cacheDiskStorage));
1611
0
  NS_ENSURE_SUCCESS(rv, rv);
1612
0
1613
0
  // For learning full URI things, we *always* open readonly and secretly, as we
1614
0
  // rely on actual pageloads to update the entry's metadata for us.
1615
0
  uint32_t uriOpenFlags = nsICacheStorage::OPEN_READONLY |
1616
0
                          nsICacheStorage::OPEN_SECRETLY |
1617
0
                          nsICacheStorage::CHECK_MULTITHREADED;
1618
0
  if (reason == nsINetworkPredictor::LEARN_LOAD_TOPLEVEL) {
1619
0
    // Learning for toplevel we want to open the full uri entry priority, since
1620
0
    // it's likely this entry will be used soon anyway, and we want this to be
1621
0
    // opened ASAP.
1622
0
    uriOpenFlags |= nsICacheStorage::OPEN_PRIORITY;
1623
0
  }
1624
0
  cacheDiskStorage->AsyncOpenURI(uriKey, EmptyCString(), uriOpenFlags,
1625
0
                                  uriAction);
1626
0
1627
0
  // Now we open the origin-only (and therefore predictor-only) entry
1628
0
  RefPtr<Predictor::Action> originAction =
1629
0
    new Predictor::Action(Predictor::Action::IS_ORIGIN,
1630
0
                          Predictor::Action::DO_LEARN, argReason, targetOrigin,
1631
0
                          sourceOrigin, nullptr, this);
1632
0
  nsAutoCString originKeyStr, targetOriginStr, sourceOriginStr;
1633
0
  originKey->GetAsciiSpec(originKeyStr);
1634
0
  targetOrigin->GetAsciiSpec(targetOriginStr);
1635
0
  if (sourceOrigin) {
1636
0
    sourceOrigin->GetAsciiSpec(sourceOriginStr);
1637
0
  }
1638
0
  PREDICTOR_LOG(("    Learn originKey=%s targetOrigin=%s sourceOrigin=%s reason=%d "
1639
0
                 "action=%p", originKeyStr.get(), targetOriginStr.get(),
1640
0
                 sourceOriginStr.get(), reason, originAction.get()));
1641
0
  uint32_t originOpenFlags;
1642
0
  if (reason == nsINetworkPredictor::LEARN_LOAD_TOPLEVEL) {
1643
0
    // This is the only case when we want to update the 'last used' metadata on
1644
0
    // the cache entry we're getting. This only applies to predictor-specific
1645
0
    // entries.
1646
0
    originOpenFlags = nsICacheStorage::OPEN_NORMALLY |
1647
0
                      nsICacheStorage::CHECK_MULTITHREADED;
1648
0
  } else {
1649
0
    originOpenFlags = nsICacheStorage::OPEN_READONLY |
1650
0
                      nsICacheStorage::OPEN_SECRETLY |
1651
0
                      nsICacheStorage::CHECK_MULTITHREADED;
1652
0
  }
1653
0
  cacheDiskStorage->AsyncOpenURI(originKey,
1654
0
                                  NS_LITERAL_CSTRING(PREDICTOR_ORIGIN_EXTENSION),
1655
0
                                  originOpenFlags, originAction);
1656
0
1657
0
  PREDICTOR_LOG(("Predictor::Learn returning"));
1658
0
  return NS_OK;
1659
0
}
1660
1661
void
1662
Predictor::LearnInternal(PredictorLearnReason reason, nsICacheEntry *entry,
1663
                         bool isNew, bool fullUri, nsIURI *targetURI,
1664
                         nsIURI *sourceURI)
1665
0
{
1666
0
  MOZ_ASSERT(NS_IsMainThread());
1667
0
1668
0
  PREDICTOR_LOG(("Predictor::LearnInternal"));
1669
0
1670
0
  nsCString junk;
1671
0
  if (!fullUri && reason == nsINetworkPredictor::LEARN_LOAD_TOPLEVEL &&
1672
0
      NS_FAILED(entry->GetMetaDataElement(SEEN_META_DATA, getter_Copies(junk)))) {
1673
0
    // This is an origin-only entry that we haven't seen before. Let's mark it
1674
0
    // as seen.
1675
0
    PREDICTOR_LOG(("    marking new origin entry as seen"));
1676
0
    nsresult rv = entry->SetMetaDataElement(SEEN_META_DATA, "1");
1677
0
    if (NS_FAILED(rv)) {
1678
0
      PREDICTOR_LOG(("    failed to mark origin entry seen"));
1679
0
      return;
1680
0
    }
1681
0
1682
0
    // Need to ensure someone else can get to the entry if necessary
1683
0
    entry->MetaDataReady();
1684
0
    return;
1685
0
  }
1686
0
1687
0
  switch (reason) {
1688
0
    case nsINetworkPredictor::LEARN_LOAD_TOPLEVEL:
1689
0
      // This case only exists to be used during tests - code outside the
1690
0
      // predictor tests should NEVER call Learn with LEARN_LOAD_TOPLEVEL.
1691
0
      // The predictor xpcshell test needs this branch, however, because we
1692
0
      // have no real page loads in xpcshell, and this is how we fake it up
1693
0
      // so that all the work that normally happens behind the scenes in a
1694
0
      // page load can be done for testing purposes.
1695
0
      if (fullUri && StaticPrefs::network_predictor_doing_tests()) {
1696
0
        PREDICTOR_LOG(("    WARNING - updating rolling load count. "
1697
0
                       "If you see this outside tests, you did it wrong"));
1698
0
1699
0
        // Since the visitor gets called under a cache lock, all we do there is get
1700
0
        // copies of the keys/values we care about, and then do the real work here
1701
0
        entry->VisitMetaData(this);
1702
0
        nsTArray<nsCString> keysToOperateOn, valuesToOperateOn;
1703
0
        keysToOperateOn.SwapElements(mKeysToOperateOn);
1704
0
        valuesToOperateOn.SwapElements(mValuesToOperateOn);
1705
0
1706
0
        MOZ_ASSERT(keysToOperateOn.Length() == valuesToOperateOn.Length());
1707
0
        for (size_t i = 0; i < keysToOperateOn.Length(); ++i) {
1708
0
          const char *key = keysToOperateOn[i].BeginReading();
1709
0
          const char *value = valuesToOperateOn[i].BeginReading();
1710
0
1711
0
          nsCString uri;
1712
0
          uint32_t hitCount, lastHit, flags;
1713
0
          if (!ParseMetaDataEntry(key, value, uri, hitCount, lastHit, flags)) {
1714
0
            // This failed, get rid of it so we don't waste space
1715
0
            entry->SetMetaDataElement(key, nullptr);
1716
0
            continue;
1717
0
          }
1718
0
          UpdateRollingLoadCount(entry, flags, key, hitCount, lastHit);
1719
0
        }
1720
0
      } else {
1721
0
        PREDICTOR_LOG(("    nothing to do for toplevel"));
1722
0
      }
1723
0
      break;
1724
0
    case nsINetworkPredictor::LEARN_LOAD_REDIRECT:
1725
0
      if (fullUri) {
1726
0
        LearnForRedirect(entry, targetURI);
1727
0
      }
1728
0
      break;
1729
0
    case nsINetworkPredictor::LEARN_LOAD_SUBRESOURCE:
1730
0
      LearnForSubresource(entry, targetURI);
1731
0
      break;
1732
0
    case nsINetworkPredictor::LEARN_STARTUP:
1733
0
      LearnForStartup(entry, targetURI);
1734
0
      break;
1735
0
    default:
1736
0
      PREDICTOR_LOG(("    unexpected reason value"));
1737
0
      MOZ_ASSERT(false, "Got unexpected value for learn reason!");
1738
0
  }
1739
0
}
1740
1741
NS_IMPL_ISUPPORTS(Predictor::SpaceCleaner, nsICacheEntryMetaDataVisitor)
1742
1743
NS_IMETHODIMP
1744
Predictor::SpaceCleaner::OnMetaDataElement(const char *key, const char *value)
1745
0
{
1746
0
  MOZ_ASSERT(NS_IsMainThread());
1747
0
1748
0
  if (!IsURIMetadataElement(key)) {
1749
0
    // This isn't a bit of metadata we care about
1750
0
    return NS_OK;
1751
0
  }
1752
0
1753
0
  nsCString uri;
1754
0
  uint32_t hitCount, lastHit, flags;
1755
0
  bool ok = mPredictor->ParseMetaDataEntry(key, value, uri, hitCount, lastHit, flags);
1756
0
1757
0
  if (!ok) {
1758
0
    // Couldn't parse this one, just get rid of it
1759
0
    nsCString nsKey;
1760
0
    nsKey.AssignASCII(key);
1761
0
    mLongKeysToDelete.AppendElement(nsKey);
1762
0
    return NS_OK;
1763
0
  }
1764
0
1765
0
  uint32_t uriLength = uri.Length();
1766
0
  if (uriLength > StaticPrefs::network_predictor_max_uri_length()) {
1767
0
    // Default to getting rid of URIs that are too long and were put in before
1768
0
    // we had our limit on URI length, in order to free up some space.
1769
0
    nsCString nsKey;
1770
0
    nsKey.AssignASCII(key);
1771
0
    mLongKeysToDelete.AppendElement(nsKey);
1772
0
    return NS_OK;
1773
0
  }
1774
0
1775
0
  if (!mLRUKeyToDelete || lastHit < mLRUStamp) {
1776
0
    mLRUKeyToDelete = key;
1777
0
    mLRUStamp = lastHit;
1778
0
  }
1779
0
1780
0
  return NS_OK;
1781
0
}
1782
1783
void
1784
Predictor::SpaceCleaner::Finalize(nsICacheEntry *entry)
1785
0
{
1786
0
  MOZ_ASSERT(NS_IsMainThread());
1787
0
1788
0
  if (mLRUKeyToDelete) {
1789
0
    entry->SetMetaDataElement(mLRUKeyToDelete, nullptr);
1790
0
  }
1791
0
1792
0
  for (size_t i = 0; i < mLongKeysToDelete.Length(); ++i) {
1793
0
    entry->SetMetaDataElement(mLongKeysToDelete[i].BeginReading(), nullptr);
1794
0
  }
1795
0
}
1796
1797
// Called when a subresource has been hit from a top-level load. Uses the two
1798
// helper functions above to update the database appropriately.
1799
void
1800
Predictor::LearnForSubresource(nsICacheEntry *entry, nsIURI *targetURI)
1801
0
{
1802
0
  MOZ_ASSERT(NS_IsMainThread());
1803
0
1804
0
  PREDICTOR_LOG(("Predictor::LearnForSubresource"));
1805
0
1806
0
  uint32_t lastLoad;
1807
0
  nsresult rv = entry->GetLastFetched(&lastLoad);
1808
0
  RETURN_IF_FAILED(rv);
1809
0
1810
0
  int32_t loadCount;
1811
0
  rv = entry->GetFetchCount(&loadCount);
1812
0
  RETURN_IF_FAILED(rv);
1813
0
1814
0
  nsCString key;
1815
0
  key.AssignLiteral(META_DATA_PREFIX);
1816
0
  nsCString uri;
1817
0
  targetURI->GetAsciiSpec(uri);
1818
0
  key.Append(uri);
1819
0
  if (uri.Length() > StaticPrefs::network_predictor_max_uri_length()) {
1820
0
    // We do this to conserve space/prevent OOMs
1821
0
    PREDICTOR_LOG(("    uri too long!"));
1822
0
    entry->SetMetaDataElement(key.BeginReading(), nullptr);
1823
0
    return;
1824
0
  }
1825
0
1826
0
  nsCString value;
1827
0
  rv = entry->GetMetaDataElement(key.BeginReading(), getter_Copies(value));
1828
0
1829
0
  uint32_t hitCount, lastHit, flags;
1830
0
  bool isNewResource = (NS_FAILED(rv) ||
1831
0
                        !ParseMetaDataEntry(key.BeginReading(),
1832
0
                                            value.BeginReading(), uri,
1833
0
                                            hitCount, lastHit, flags));
1834
0
1835
0
  int32_t resourceCount = 0;
1836
0
  if (isNewResource) {
1837
0
    // This is a new addition
1838
0
    PREDICTOR_LOG(("    new resource"));
1839
0
    nsCString s;
1840
0
    rv = entry->GetMetaDataElement(RESOURCE_META_DATA, getter_Copies(s));
1841
0
    if (NS_SUCCEEDED(rv)) {
1842
0
      resourceCount = atoi(s.BeginReading());
1843
0
    }
1844
0
    if (resourceCount >=
1845
0
        StaticPrefs::network_predictor_max_resources_per_entry()) {
1846
0
      RefPtr<Predictor::SpaceCleaner> cleaner =
1847
0
        new Predictor::SpaceCleaner(this);
1848
0
      entry->VisitMetaData(cleaner);
1849
0
      cleaner->Finalize(entry);
1850
0
    } else {
1851
0
      ++resourceCount;
1852
0
    }
1853
0
    nsAutoCString count;
1854
0
    count.AppendInt(resourceCount);
1855
0
    rv = entry->SetMetaDataElement(RESOURCE_META_DATA, count.BeginReading());
1856
0
    if (NS_FAILED(rv)) {
1857
0
      PREDICTOR_LOG(("    failed to update resource count"));
1858
0
      return;
1859
0
    }
1860
0
    hitCount = 1;
1861
0
    flags = 0;
1862
0
  } else {
1863
0
    PREDICTOR_LOG(("    existing resource"));
1864
0
    hitCount = std::min(hitCount + 1, static_cast<uint32_t>(loadCount));
1865
0
  }
1866
0
1867
0
  // Update the rolling load count to mark this sub-resource as seen on the
1868
0
  // most-recent pageload so it can be eligible for prefetch (assuming all
1869
0
  // the other stars align).
1870
0
  flags |= (1 << kRollingLoadOffset);
1871
0
1872
0
  nsCString newValue;
1873
0
  MakeMetadataEntry(hitCount, lastLoad, flags, newValue);
1874
0
  rv = entry->SetMetaDataElement(key.BeginReading(), newValue.BeginReading());
1875
0
  PREDICTOR_LOG(("    SetMetaDataElement -> 0x%08" PRIX32, static_cast<uint32_t>(rv)));
1876
0
  if (NS_FAILED(rv) && isNewResource) {
1877
0
    // Roll back the increment to the resource count we made above.
1878
0
    PREDICTOR_LOG(("    rolling back resource count update"));
1879
0
    --resourceCount;
1880
0
    if (resourceCount == 0) {
1881
0
      entry->SetMetaDataElement(RESOURCE_META_DATA, nullptr);
1882
0
    } else {
1883
0
      nsAutoCString count;
1884
0
      count.AppendInt(resourceCount);
1885
0
      entry->SetMetaDataElement(RESOURCE_META_DATA, count.BeginReading());
1886
0
    }
1887
0
  }
1888
0
}
1889
1890
// This is called when a top-level loaded ended up redirecting to a different
1891
// URI so we can keep track of that fact.
1892
void
1893
Predictor::LearnForRedirect(nsICacheEntry *entry, nsIURI *targetURI)
1894
0
{
1895
0
  MOZ_ASSERT(NS_IsMainThread());
1896
0
1897
0
  // TODO - not doing redirects for first go around
1898
0
  PREDICTOR_LOG(("Predictor::LearnForRedirect"));
1899
0
}
1900
1901
// This will add a page to our list of startup pages if it's being loaded
1902
// before our startup window has expired.
1903
void
1904
Predictor::MaybeLearnForStartup(nsIURI *uri, bool fullUri,
1905
                                const OriginAttributes& originAttributes)
1906
0
{
1907
0
  MOZ_ASSERT(NS_IsMainThread());
1908
0
1909
0
  // TODO - not doing startup for first go around
1910
0
  PREDICTOR_LOG(("Predictor::MaybeLearnForStartup"));
1911
0
}
1912
1913
// Add information about a top-level load to our list of startup pages
1914
void
1915
Predictor::LearnForStartup(nsICacheEntry *entry, nsIURI *targetURI)
1916
0
{
1917
0
  MOZ_ASSERT(NS_IsMainThread());
1918
0
1919
0
  // These actually do the same set of work, just on different entries, so we
1920
0
  // can pass through to get the real work done here
1921
0
  PREDICTOR_LOG(("Predictor::LearnForStartup"));
1922
0
  LearnForSubresource(entry, targetURI);
1923
0
}
1924
1925
bool
1926
Predictor::ParseMetaDataEntry(const char *key, const char *value, nsCString &uri,
1927
                              uint32_t &hitCount, uint32_t &lastHit,
1928
                              uint32_t &flags)
1929
0
{
1930
0
  MOZ_ASSERT(NS_IsMainThread());
1931
0
1932
0
  PREDICTOR_LOG(("Predictor::ParseMetaDataEntry key=%s value=%s",
1933
0
                 key ? key : "", value));
1934
0
1935
0
  const char *comma = strchr(value, ',');
1936
0
  if (!comma) {
1937
0
    PREDICTOR_LOG(("    could not find first comma"));
1938
0
    return false;
1939
0
  }
1940
0
1941
0
  uint32_t version = static_cast<uint32_t>(atoi(value));
1942
0
  PREDICTOR_LOG(("    version -> %u", version));
1943
0
1944
0
  if (version != METADATA_VERSION) {
1945
0
    PREDICTOR_LOG(("    metadata version mismatch %u != %u", version,
1946
0
                   METADATA_VERSION));
1947
0
    return false;
1948
0
  }
1949
0
1950
0
  value = comma + 1;
1951
0
  comma = strchr(value, ',');
1952
0
  if (!comma) {
1953
0
    PREDICTOR_LOG(("    could not find second comma"));
1954
0
    return false;
1955
0
  }
1956
0
1957
0
  hitCount = static_cast<uint32_t>(atoi(value));
1958
0
  PREDICTOR_LOG(("    hitCount -> %u", hitCount));
1959
0
1960
0
  value = comma + 1;
1961
0
  comma = strchr(value, ',');
1962
0
  if (!comma) {
1963
0
    PREDICTOR_LOG(("    could not find third comma"));
1964
0
    return false;
1965
0
  }
1966
0
1967
0
  lastHit = static_cast<uint32_t>(atoi(value));
1968
0
  PREDICTOR_LOG(("    lastHit -> %u", lastHit));
1969
0
1970
0
  value = comma + 1;
1971
0
  flags = static_cast<uint32_t>(atoi(value));
1972
0
  PREDICTOR_LOG(("    flags -> %u", flags));
1973
0
1974
0
  if (key) {
1975
0
    const char *uriStart = key + (sizeof(META_DATA_PREFIX) - 1);
1976
0
    uri.AssignASCII(uriStart);
1977
0
    PREDICTOR_LOG(("    uri -> %s", uriStart));
1978
0
  } else {
1979
0
    uri.Truncate();
1980
0
  }
1981
0
1982
0
  return true;
1983
0
}
1984
1985
NS_IMETHODIMP
1986
Predictor::Reset()
1987
0
{
1988
0
  MOZ_ASSERT(NS_IsMainThread(),
1989
0
             "Predictor interface methods must be called on the main thread");
1990
0
1991
0
  PREDICTOR_LOG(("Predictor::Reset"));
1992
0
1993
0
  if (IsNeckoChild()) {
1994
0
    MOZ_DIAGNOSTIC_ASSERT(gNeckoChild);
1995
0
1996
0
    PREDICTOR_LOG(("    forwarding to parent process"));
1997
0
    gNeckoChild->SendPredReset();
1998
0
    return NS_OK;
1999
0
  }
2000
0
2001
0
  PREDICTOR_LOG(("    called on parent process"));
2002
0
2003
0
  if (!mInitialized) {
2004
0
    PREDICTOR_LOG(("    not initialized"));
2005
0
    return NS_OK;
2006
0
  }
2007
0
2008
0
  if (!StaticPrefs::network_predictor_enabled()) {
2009
0
    PREDICTOR_LOG(("    not enabled"));
2010
0
    return NS_OK;
2011
0
  }
2012
0
2013
0
  RefPtr<Predictor::Resetter> reset = new Predictor::Resetter(this);
2014
0
  PREDICTOR_LOG(("    created a resetter"));
2015
0
  mCacheStorageService->AsyncVisitAllStorages(reset, true);
2016
0
  PREDICTOR_LOG(("    Cache async launched, returning now"));
2017
0
2018
0
  return NS_OK;
2019
0
}
2020
2021
NS_IMPL_ISUPPORTS(Predictor::Resetter,
2022
                  nsICacheEntryOpenCallback,
2023
                  nsICacheEntryMetaDataVisitor,
2024
                  nsICacheStorageVisitor);
2025
2026
Predictor::Resetter::Resetter(Predictor *predictor)
2027
  :mEntriesToVisit(0)
2028
  ,mPredictor(predictor)
2029
0
{ }
2030
2031
NS_IMETHODIMP
2032
Predictor::Resetter::OnCacheEntryCheck(nsICacheEntry *entry,
2033
                                       nsIApplicationCache *appCache,
2034
                                       uint32_t *result)
2035
0
{
2036
0
  *result = nsICacheEntryOpenCallback::ENTRY_WANTED;
2037
0
  return NS_OK;
2038
0
}
2039
2040
NS_IMETHODIMP
2041
Predictor::Resetter::OnCacheEntryAvailable(nsICacheEntry *entry, bool isNew,
2042
                                           nsIApplicationCache *appCache,
2043
                                           nsresult result)
2044
0
{
2045
0
  MOZ_ASSERT(NS_IsMainThread());
2046
0
2047
0
  if (NS_FAILED(result)) {
2048
0
    // This can happen when we've tried to open an entry that doesn't exist for
2049
0
    // some non-reset operation, and then get reset shortly thereafter (as
2050
0
    // happens in some of our tests).
2051
0
    --mEntriesToVisit;
2052
0
    if (!mEntriesToVisit) {
2053
0
      Complete();
2054
0
    }
2055
0
    return NS_OK;
2056
0
  }
2057
0
2058
0
  entry->VisitMetaData(this);
2059
0
  nsTArray<nsCString> keysToDelete;
2060
0
  keysToDelete.SwapElements(mKeysToDelete);
2061
0
2062
0
  for (size_t i = 0; i < keysToDelete.Length(); ++i) {
2063
0
    const char *key = keysToDelete[i].BeginReading();
2064
0
    entry->SetMetaDataElement(key, nullptr);
2065
0
  }
2066
0
2067
0
  --mEntriesToVisit;
2068
0
  if (!mEntriesToVisit) {
2069
0
    Complete();
2070
0
  }
2071
0
2072
0
  return NS_OK;
2073
0
}
2074
2075
NS_IMETHODIMP
2076
Predictor::Resetter::OnMetaDataElement(const char *asciiKey,
2077
                                       const char *asciiValue)
2078
0
{
2079
0
  MOZ_ASSERT(NS_IsMainThread());
2080
0
2081
0
  if (!StringBeginsWith(nsDependentCString(asciiKey),
2082
0
                        NS_LITERAL_CSTRING(META_DATA_PREFIX))) {
2083
0
    // Not a metadata entry we care about, carry on
2084
0
    return NS_OK;
2085
0
  }
2086
0
2087
0
  nsCString key;
2088
0
  key.AssignASCII(asciiKey);
2089
0
  mKeysToDelete.AppendElement(key);
2090
0
2091
0
  return NS_OK;
2092
0
}
2093
2094
NS_IMETHODIMP
2095
Predictor::Resetter::OnCacheStorageInfo(uint32_t entryCount, uint64_t consumption,
2096
                                        uint64_t capacity, nsIFile *diskDirectory)
2097
0
{
2098
0
  MOZ_ASSERT(NS_IsMainThread());
2099
0
2100
0
  return NS_OK;
2101
0
}
2102
2103
NS_IMETHODIMP
2104
Predictor::Resetter::OnCacheEntryInfo(nsIURI *uri, const nsACString &idEnhance,
2105
                                      int64_t dataSize, int32_t fetchCount,
2106
                                      uint32_t lastModifiedTime, uint32_t expirationTime,
2107
                                      bool aPinned, nsILoadContextInfo* aInfo)
2108
0
{
2109
0
  MOZ_ASSERT(NS_IsMainThread());
2110
0
2111
0
  nsresult rv;
2112
0
2113
0
  // The predictor will only ever touch entries with no idEnhance ("") or an
2114
0
  // idEnhance of PREDICTOR_ORIGIN_EXTENSION, so we filter out any entries that
2115
0
  // don't match that to avoid doing extra work.
2116
0
  if (idEnhance.EqualsLiteral(PREDICTOR_ORIGIN_EXTENSION)) {
2117
0
    // This is an entry we own, so we can just doom it entirely
2118
0
    nsCOMPtr<nsICacheStorage> cacheDiskStorage;
2119
0
2120
0
    rv = mPredictor->mCacheStorageService
2121
0
                   ->DiskCacheStorage(aInfo, false,
2122
0
                                      getter_AddRefs(cacheDiskStorage));
2123
0
2124
0
    NS_ENSURE_SUCCESS(rv, rv);
2125
0
    cacheDiskStorage->AsyncDoomURI(uri, idEnhance, nullptr);
2126
0
  } else if (idEnhance.IsEmpty()) {
2127
0
    // This is an entry we don't own, so we have to be a little more careful and
2128
0
    // just get rid of our own metadata entries. Append it to an array of things
2129
0
    // to operate on and then do the operations later so we don't end up calling
2130
0
    // Complete() multiple times/too soon.
2131
0
    ++mEntriesToVisit;
2132
0
    mURIsToVisit.AppendElement(uri);
2133
0
    mInfosToVisit.AppendElement(aInfo);
2134
0
  }
2135
0
2136
0
  return NS_OK;
2137
0
}
2138
2139
NS_IMETHODIMP
2140
Predictor::Resetter::OnCacheEntryVisitCompleted()
2141
0
{
2142
0
  MOZ_ASSERT(NS_IsMainThread());
2143
0
2144
0
  nsresult rv;
2145
0
2146
0
  nsTArray<nsCOMPtr<nsIURI>> urisToVisit;
2147
0
  urisToVisit.SwapElements(mURIsToVisit);
2148
0
2149
0
  MOZ_ASSERT(mEntriesToVisit == urisToVisit.Length());
2150
0
2151
0
  nsTArray<nsCOMPtr<nsILoadContextInfo>> infosToVisit;
2152
0
  infosToVisit.SwapElements(mInfosToVisit);
2153
0
2154
0
  MOZ_ASSERT(mEntriesToVisit == infosToVisit.Length());
2155
0
2156
0
  if (!mEntriesToVisit) {
2157
0
    Complete();
2158
0
    return NS_OK;
2159
0
  }
2160
0
2161
0
  uint32_t entriesToVisit = urisToVisit.Length();
2162
0
  for (uint32_t i = 0; i < entriesToVisit; ++i) {
2163
0
    nsCString u;
2164
0
    nsCOMPtr<nsICacheStorage> cacheDiskStorage;
2165
0
2166
0
    rv = mPredictor->mCacheStorageService
2167
0
                   ->DiskCacheStorage(infosToVisit[i], false,
2168
0
                                      getter_AddRefs(cacheDiskStorage));
2169
0
    NS_ENSURE_SUCCESS(rv, rv);
2170
0
2171
0
2172
0
    urisToVisit[i]->GetAsciiSpec(u);
2173
0
    cacheDiskStorage->AsyncOpenURI(
2174
0
        urisToVisit[i], EmptyCString(),
2175
0
        nsICacheStorage::OPEN_READONLY | nsICacheStorage::OPEN_SECRETLY | nsICacheStorage::CHECK_MULTITHREADED,
2176
0
        this);
2177
0
  }
2178
0
2179
0
  return NS_OK;
2180
0
}
2181
2182
void
2183
Predictor::Resetter::Complete()
2184
0
{
2185
0
  MOZ_ASSERT(NS_IsMainThread());
2186
0
2187
0
  nsCOMPtr<nsIObserverService> obs = mozilla::services::GetObserverService();
2188
0
  if (!obs) {
2189
0
    PREDICTOR_LOG(("COULD NOT GET OBSERVER SERVICE!"));
2190
0
    return;
2191
0
  }
2192
0
2193
0
  obs->NotifyObservers(nullptr, "predictor-reset-complete", nullptr);
2194
0
}
2195
2196
// Helper functions to make using the predictor easier from native code
2197
2198
static StaticRefPtr<nsINetworkPredictor> sPredictor;
2199
2200
static nsresult
2201
EnsureGlobalPredictor(nsINetworkPredictor **aPredictor)
2202
0
{
2203
0
  MOZ_ASSERT(NS_IsMainThread());
2204
0
2205
0
  if (!sPredictor) {
2206
0
    nsresult rv;
2207
0
    nsCOMPtr<nsINetworkPredictor> predictor =
2208
0
      do_GetService("@mozilla.org/network/predictor;1",
2209
0
                    &rv);
2210
0
    NS_ENSURE_SUCCESS(rv, rv);
2211
0
    sPredictor = predictor;
2212
0
    ClearOnShutdown(&sPredictor);
2213
0
  }
2214
0
2215
0
  nsCOMPtr<nsINetworkPredictor> predictor = sPredictor.get();
2216
0
  predictor.forget(aPredictor);
2217
0
  return NS_OK;
2218
0
}
2219
2220
nsresult
2221
PredictorPredict(nsIURI *targetURI, nsIURI *sourceURI,
2222
                 PredictorPredictReason reason,
2223
                 const OriginAttributes& originAttributes,
2224
                 nsINetworkPredictorVerifier *verifier)
2225
0
{
2226
0
  MOZ_ASSERT(NS_IsMainThread());
2227
0
2228
0
  if (!IsNullOrHttp(targetURI) || !IsNullOrHttp(sourceURI)) {
2229
0
    return NS_OK;
2230
0
  }
2231
0
2232
0
  nsCOMPtr<nsINetworkPredictor> predictor;
2233
0
  nsresult rv = EnsureGlobalPredictor(getter_AddRefs(predictor));
2234
0
  NS_ENSURE_SUCCESS(rv, rv);
2235
0
2236
0
  return predictor->PredictNative(targetURI, sourceURI, reason,
2237
0
                                  originAttributes, verifier);
2238
0
}
2239
2240
nsresult
2241
PredictorLearn(nsIURI *targetURI, nsIURI *sourceURI,
2242
               PredictorLearnReason reason,
2243
               const OriginAttributes& originAttributes)
2244
0
{
2245
0
  MOZ_ASSERT(NS_IsMainThread());
2246
0
2247
0
  if (!IsNullOrHttp(targetURI) || !IsNullOrHttp(sourceURI)) {
2248
0
    return NS_OK;
2249
0
  }
2250
0
2251
0
  nsCOMPtr<nsINetworkPredictor> predictor;
2252
0
  nsresult rv = EnsureGlobalPredictor(getter_AddRefs(predictor));
2253
0
  NS_ENSURE_SUCCESS(rv, rv);
2254
0
2255
0
  return predictor->LearnNative(targetURI, sourceURI, reason, originAttributes);
2256
0
}
2257
2258
nsresult
2259
PredictorLearn(nsIURI *targetURI, nsIURI *sourceURI,
2260
               PredictorLearnReason reason,
2261
               nsILoadGroup *loadGroup)
2262
0
{
2263
0
  MOZ_ASSERT(NS_IsMainThread());
2264
0
2265
0
  if (!IsNullOrHttp(targetURI) || !IsNullOrHttp(sourceURI)) {
2266
0
    return NS_OK;
2267
0
  }
2268
0
2269
0
  nsCOMPtr<nsINetworkPredictor> predictor;
2270
0
  nsresult rv = EnsureGlobalPredictor(getter_AddRefs(predictor));
2271
0
  NS_ENSURE_SUCCESS(rv, rv);
2272
0
2273
0
  nsCOMPtr<nsILoadContext> loadContext;
2274
0
  OriginAttributes originAttributes;
2275
0
2276
0
  if (loadGroup) {
2277
0
    nsCOMPtr<nsIInterfaceRequestor> callbacks;
2278
0
    loadGroup->GetNotificationCallbacks(getter_AddRefs(callbacks));
2279
0
    if (callbacks) {
2280
0
      loadContext = do_GetInterface(callbacks);
2281
0
2282
0
      if (loadContext) {
2283
0
        loadContext->GetOriginAttributes(originAttributes);
2284
0
      }
2285
0
    }
2286
0
  }
2287
0
2288
0
  return predictor->LearnNative(targetURI, sourceURI, reason, originAttributes);
2289
0
}
2290
2291
nsresult
2292
PredictorLearn(nsIURI *targetURI, nsIURI *sourceURI,
2293
               PredictorLearnReason reason,
2294
               nsIDocument *document)
2295
0
{
2296
0
  MOZ_ASSERT(NS_IsMainThread());
2297
0
2298
0
  if (!IsNullOrHttp(targetURI) || !IsNullOrHttp(sourceURI)) {
2299
0
    return NS_OK;
2300
0
  }
2301
0
2302
0
  nsCOMPtr<nsINetworkPredictor> predictor;
2303
0
  nsresult rv = EnsureGlobalPredictor(getter_AddRefs(predictor));
2304
0
  NS_ENSURE_SUCCESS(rv, rv);
2305
0
2306
0
  OriginAttributes originAttributes;
2307
0
2308
0
  if (document) {
2309
0
    nsCOMPtr<nsIPrincipal> docPrincipal = document->NodePrincipal();
2310
0
2311
0
    if (docPrincipal) {
2312
0
      originAttributes = docPrincipal->OriginAttributesRef();
2313
0
    }
2314
0
  }
2315
0
2316
0
  return predictor->LearnNative(targetURI, sourceURI, reason, originAttributes);
2317
0
}
2318
2319
nsresult
2320
PredictorLearnRedirect(nsIURI *targetURI, nsIChannel *channel,
2321
                       const OriginAttributes& originAttributes)
2322
0
{
2323
0
  MOZ_ASSERT(NS_IsMainThread());
2324
0
2325
0
  nsCOMPtr<nsIURI> sourceURI;
2326
0
  nsresult rv = channel->GetOriginalURI(getter_AddRefs(sourceURI));
2327
0
  NS_ENSURE_SUCCESS(rv, rv);
2328
0
2329
0
  bool sameUri;
2330
0
  rv = targetURI->Equals(sourceURI, &sameUri);
2331
0
  NS_ENSURE_SUCCESS(rv, rv);
2332
0
2333
0
  if (sameUri) {
2334
0
    return NS_OK;
2335
0
  }
2336
0
2337
0
  if (!IsNullOrHttp(targetURI) || !IsNullOrHttp(sourceURI)) {
2338
0
    return NS_OK;
2339
0
  }
2340
0
2341
0
  nsCOMPtr<nsINetworkPredictor> predictor;
2342
0
  rv = EnsureGlobalPredictor(getter_AddRefs(predictor));
2343
0
  NS_ENSURE_SUCCESS(rv, rv);
2344
0
2345
0
  return predictor->LearnNative(targetURI, sourceURI,
2346
0
                                nsINetworkPredictor::LEARN_LOAD_REDIRECT,
2347
0
                                originAttributes);
2348
0
}
2349
2350
// nsINetworkPredictorVerifier
2351
2352
/**
2353
 * Call through to the child's verifier (only during tests)
2354
 */
2355
NS_IMETHODIMP
2356
Predictor::OnPredictPrefetch(nsIURI *aURI, uint32_t httpStatus)
2357
0
{
2358
0
  if (IsNeckoChild()) {
2359
0
    if (mChildVerifier) {
2360
0
      // Ideally, we'd assert here. But since we're slowly moving towards a
2361
0
      // world where we have multiple child processes, and only one child process
2362
0
      // will be likely to have a verifier, we have to play it safer.
2363
0
      return mChildVerifier->OnPredictPrefetch(aURI, httpStatus);
2364
0
    }
2365
0
    return NS_OK;
2366
0
  }
2367
0
2368
0
  ipc::URIParams serURI;
2369
0
  SerializeURI(aURI, serURI);
2370
0
2371
0
  for (auto* cp : ContentParent::AllProcesses(ContentParent::eLive)) {
2372
0
    PNeckoParent* neckoParent = SingleManagedOrNull(cp->ManagedPNeckoParent());
2373
0
    if (!neckoParent) {
2374
0
      continue;
2375
0
    }
2376
0
    if (!neckoParent->SendPredOnPredictPrefetch(serURI, httpStatus)) {
2377
0
      return NS_ERROR_NOT_AVAILABLE;
2378
0
    }
2379
0
  }
2380
0
2381
0
  return NS_OK;
2382
0
}
2383
2384
NS_IMETHODIMP
2385
0
Predictor::OnPredictPreconnect(nsIURI *aURI) {
2386
0
  if (IsNeckoChild()) {
2387
0
    if (mChildVerifier) {
2388
0
      // Ideally, we'd assert here. But since we're slowly moving towards a
2389
0
      // world where we have multiple child processes, and only one child process
2390
0
      // will be likely to have a verifier, we have to play it safer.
2391
0
      return mChildVerifier->OnPredictPreconnect(aURI);
2392
0
    }
2393
0
    return NS_OK;
2394
0
  }
2395
0
2396
0
  ipc::URIParams serURI;
2397
0
  SerializeURI(aURI, serURI);
2398
0
2399
0
  for (auto* cp : ContentParent::AllProcesses(ContentParent::eLive)) {
2400
0
    PNeckoParent* neckoParent = SingleManagedOrNull(cp->ManagedPNeckoParent());
2401
0
    if (!neckoParent) {
2402
0
      continue;
2403
0
    }
2404
0
    if (!neckoParent->SendPredOnPredictPreconnect(serURI)) {
2405
0
      return NS_ERROR_NOT_AVAILABLE;
2406
0
    }
2407
0
  }
2408
0
2409
0
  return NS_OK;
2410
0
}
2411
2412
NS_IMETHODIMP
2413
0
Predictor::OnPredictDNS(nsIURI *aURI) {
2414
0
  if (IsNeckoChild()) {
2415
0
    if (mChildVerifier) {
2416
0
      // Ideally, we'd assert here. But since we're slowly moving towards a
2417
0
      // world where we have multiple child processes, and only one child process
2418
0
      // will be likely to have a verifier, we have to play it safer.
2419
0
      return mChildVerifier->OnPredictDNS(aURI);
2420
0
    }
2421
0
    return NS_OK;
2422
0
  }
2423
0
2424
0
  ipc::URIParams serURI;
2425
0
  SerializeURI(aURI, serURI);
2426
0
2427
0
  for (auto* cp : ContentParent::AllProcesses(ContentParent::eLive)) {
2428
0
    PNeckoParent* neckoParent = SingleManagedOrNull(cp->ManagedPNeckoParent());
2429
0
    if (!neckoParent) {
2430
0
      continue;
2431
0
    }
2432
0
    if (!neckoParent->SendPredOnPredictDNS(serURI)) {
2433
0
      return NS_ERROR_NOT_AVAILABLE;
2434
0
    }
2435
0
  }
2436
0
2437
0
  return NS_OK;
2438
0
}
2439
2440
// Predictor::PrefetchListener
2441
// nsISupports
2442
NS_IMPL_ISUPPORTS(Predictor::PrefetchListener,
2443
                  nsIStreamListener,
2444
                  nsIRequestObserver)
2445
2446
// nsIRequestObserver
2447
NS_IMETHODIMP
2448
Predictor::PrefetchListener::OnStartRequest(nsIRequest *aRequest,
2449
                                            nsISupports *aContext)
2450
0
{
2451
0
  mStartTime = TimeStamp::Now();
2452
0
  return NS_OK;
2453
0
}
2454
2455
NS_IMETHODIMP
2456
Predictor::PrefetchListener::OnStopRequest(nsIRequest *aRequest,
2457
                                           nsISupports *aContext,
2458
                                           nsresult aStatusCode)
2459
0
{
2460
0
  PREDICTOR_LOG(("OnStopRequest this=%p aStatusCode=0x%" PRIX32,
2461
0
                 this, static_cast<uint32_t>(aStatusCode)));
2462
0
  NS_ENSURE_ARG(aRequest);
2463
0
  if (NS_FAILED(aStatusCode)) {
2464
0
    return aStatusCode;
2465
0
  }
2466
0
  Telemetry::AccumulateTimeDelta(Telemetry::PREDICTOR_PREFETCH_TIME, mStartTime);
2467
0
2468
0
  nsCOMPtr<nsIHttpChannel> httpChannel = do_QueryInterface(aRequest);
2469
0
  if (!httpChannel) {
2470
0
    PREDICTOR_LOG(("    Could not get HTTP Channel!"));
2471
0
    return NS_ERROR_UNEXPECTED;
2472
0
  }
2473
0
  nsCOMPtr<nsICachingChannel> cachingChannel = do_QueryInterface(httpChannel);
2474
0
  if (!cachingChannel) {
2475
0
    PREDICTOR_LOG(("    Could not get caching channel!"));
2476
0
    return NS_ERROR_UNEXPECTED;
2477
0
  }
2478
0
2479
0
  nsresult rv = NS_OK;
2480
0
  uint32_t httpStatus;
2481
0
  rv = httpChannel->GetResponseStatus(&httpStatus);
2482
0
  if (NS_SUCCEEDED(rv) && httpStatus == 200) {
2483
0
    rv = cachingChannel->ForceCacheEntryValidFor(
2484
0
           StaticPrefs::network_predictor_prefetch_force_valid_for());
2485
0
    PREDICTOR_LOG(("    forcing entry valid for %d seconds rv=%" PRIX32,
2486
0
                   StaticPrefs::network_predictor_prefetch_force_valid_for(),
2487
0
                   static_cast<uint32_t>(rv)));
2488
0
  } else {
2489
0
    rv = cachingChannel->ForceCacheEntryValidFor(0);
2490
0
    PREDICTOR_LOG(("    removing any forced validity rv=%" PRIX32,
2491
0
                   static_cast<uint32_t>(rv)));
2492
0
  }
2493
0
2494
0
  nsAutoCString reqName;
2495
0
  rv = aRequest->GetName(reqName);
2496
0
  if (NS_FAILED(rv)) {
2497
0
    reqName.AssignLiteral("<unknown>");
2498
0
  }
2499
0
2500
0
  PREDICTOR_LOG(("    request %s status %u", reqName.get(), httpStatus));
2501
0
2502
0
  if (mVerifier) {
2503
0
    mVerifier->OnPredictPrefetch(mURI, httpStatus);
2504
0
  }
2505
0
2506
0
  return rv;
2507
0
}
2508
2509
// nsIStreamListener
2510
NS_IMETHODIMP
2511
Predictor::PrefetchListener::OnDataAvailable(nsIRequest *aRequest,
2512
                                             nsISupports *aContext,
2513
                                             nsIInputStream *aInputStream,
2514
                                             uint64_t aOffset,
2515
                                             const uint32_t aCount)
2516
0
{
2517
0
  uint32_t result;
2518
0
  return aInputStream->ReadSegments(NS_DiscardSegment, nullptr, aCount, &result);
2519
0
}
2520
2521
// Miscellaneous Predictor
2522
2523
void
2524
Predictor::UpdateCacheability(nsIURI *sourceURI, nsIURI *targetURI,
2525
                              uint32_t httpStatus,
2526
                              nsHttpRequestHead &requestHead,
2527
                              nsHttpResponseHead *responseHead,
2528
                              nsILoadContextInfo *lci, bool isTracking)
2529
0
{
2530
0
  MOZ_ASSERT(NS_IsMainThread());
2531
0
2532
0
  if (lci && lci->IsPrivate()) {
2533
0
    PREDICTOR_LOG(("Predictor::UpdateCacheability in PB mode - ignoring"));
2534
0
    return;
2535
0
  }
2536
0
2537
0
  if (!sourceURI || !targetURI) {
2538
0
    PREDICTOR_LOG(("Predictor::UpdateCacheability missing source or target uri"));
2539
0
    return;
2540
0
  }
2541
0
2542
0
  if (!IsNullOrHttp(sourceURI) || !IsNullOrHttp(targetURI)) {
2543
0
    PREDICTOR_LOG(("Predictor::UpdateCacheability non-http(s) uri"));
2544
0
    return;
2545
0
  }
2546
0
2547
0
  RefPtr<Predictor> self = sSelf;
2548
0
  if (self) {
2549
0
    nsAutoCString method;
2550
0
    requestHead.Method(method);
2551
0
2552
0
    nsAutoCString vary;
2553
0
    Unused << responseHead->GetHeader(nsHttp::Vary, vary);
2554
0
2555
0
    nsAutoCString cacheControlHeader;
2556
0
    Unused << responseHead->GetHeader(nsHttp::Cache_Control, cacheControlHeader);
2557
0
    CacheControlParser cacheControl(cacheControlHeader);
2558
0
2559
0
    self->UpdateCacheabilityInternal(sourceURI, targetURI, httpStatus,
2560
0
                                     method, *lci->OriginAttributesPtr(),
2561
0
                                     isTracking, !vary.IsEmpty(),
2562
0
                                     cacheControl.NoStore());
2563
0
  }
2564
0
}
2565
2566
void
2567
Predictor::UpdateCacheabilityInternal(nsIURI *sourceURI, nsIURI *targetURI,
2568
                                      uint32_t httpStatus,
2569
                                      const nsCString &method,
2570
                                      const OriginAttributes& originAttributes,
2571
                                      bool isTracking, bool couldVary,
2572
                                      bool isNoStore)
2573
0
{
2574
0
  PREDICTOR_LOG(("Predictor::UpdateCacheability httpStatus=%u", httpStatus));
2575
0
2576
0
  nsresult rv;
2577
0
2578
0
  if (!mInitialized) {
2579
0
    PREDICTOR_LOG(("    not initialized"));
2580
0
    return;
2581
0
  }
2582
0
2583
0
  if (!StaticPrefs::network_predictor_enabled()) {
2584
0
    PREDICTOR_LOG(("    not enabled"));
2585
0
    return;
2586
0
  }
2587
0
2588
0
  nsCOMPtr<nsICacheStorage> cacheDiskStorage;
2589
0
2590
0
  RefPtr<LoadContextInfo> lci =
2591
0
    new LoadContextInfo(false, originAttributes);
2592
0
2593
0
  rv = mCacheStorageService->DiskCacheStorage(lci, false,
2594
0
                                             getter_AddRefs(cacheDiskStorage));
2595
0
  if (NS_FAILED(rv)) {
2596
0
    PREDICTOR_LOG(("    cannot get disk cache storage"));
2597
0
    return;
2598
0
  }
2599
0
2600
0
  uint32_t openFlags = nsICacheStorage::OPEN_READONLY |
2601
0
                       nsICacheStorage::OPEN_SECRETLY |
2602
0
                       nsICacheStorage::CHECK_MULTITHREADED;
2603
0
  RefPtr<Predictor::CacheabilityAction> action =
2604
0
    new Predictor::CacheabilityAction(targetURI, httpStatus, method, isTracking,
2605
0
                                      couldVary, isNoStore, this);
2606
0
  nsAutoCString uri;
2607
0
  targetURI->GetAsciiSpec(uri);
2608
0
  PREDICTOR_LOG(("    uri=%s action=%p", uri.get(), action.get()));
2609
0
  cacheDiskStorage->AsyncOpenURI(sourceURI, EmptyCString(), openFlags, action);
2610
0
}
2611
2612
NS_IMPL_ISUPPORTS(Predictor::CacheabilityAction,
2613
                  nsICacheEntryOpenCallback,
2614
                  nsICacheEntryMetaDataVisitor);
2615
2616
NS_IMETHODIMP
2617
Predictor::CacheabilityAction::OnCacheEntryCheck(nsICacheEntry *entry,
2618
                                                 nsIApplicationCache *appCache,
2619
                                                 uint32_t *result)
2620
0
{
2621
0
  *result = nsICacheEntryOpenCallback::ENTRY_WANTED;
2622
0
  return NS_OK;
2623
0
}
2624
2625
namespace {
2626
enum PrefetchDecisionReason {
2627
  PREFETCHABLE,
2628
  STATUS_NOT_200,
2629
  METHOD_NOT_GET,
2630
  URL_HAS_QUERY_STRING,
2631
  RESOURCE_IS_TRACKING,
2632
  RESOURCE_COULD_VARY,
2633
  RESOURCE_IS_NO_STORE
2634
};
2635
}
2636
2637
NS_IMETHODIMP
2638
Predictor::CacheabilityAction::OnCacheEntryAvailable(nsICacheEntry *entry,
2639
                                                     bool isNew,
2640
                                                     nsIApplicationCache *appCache,
2641
                                                     nsresult result)
2642
0
{
2643
0
  MOZ_ASSERT(NS_IsMainThread());
2644
0
  // This is being opened read-only, so isNew should always be false
2645
0
  MOZ_ASSERT(!isNew);
2646
0
2647
0
  PREDICTOR_LOG(("CacheabilityAction::OnCacheEntryAvailable this=%p", this));
2648
0
  if (NS_FAILED(result)) {
2649
0
    // Nothing to do
2650
0
    PREDICTOR_LOG(("    nothing to do result=%" PRIX32 " isNew=%d",
2651
0
                   static_cast<uint32_t>(result), isNew));
2652
0
    return NS_OK;
2653
0
  }
2654
0
2655
0
  nsCString strTargetURI;
2656
0
  nsresult rv = mTargetURI->GetAsciiSpec(strTargetURI);
2657
0
  if (NS_FAILED(rv)) {
2658
0
    PREDICTOR_LOG(("    GetAsciiSpec returned %" PRIx32, static_cast<uint32_t>(rv)));
2659
0
    return NS_OK;
2660
0
  }
2661
0
2662
0
  rv = entry->VisitMetaData(this);
2663
0
  if (NS_FAILED(rv)) {
2664
0
    PREDICTOR_LOG(("    VisitMetaData returned %" PRIx32, static_cast<uint32_t>(rv)));
2665
0
    return NS_OK;
2666
0
  }
2667
0
2668
0
  nsTArray<nsCString> keysToCheck, valuesToCheck;
2669
0
  keysToCheck.SwapElements(mKeysToCheck);
2670
0
  valuesToCheck.SwapElements(mValuesToCheck);
2671
0
2672
0
  bool hasQueryString = false;
2673
0
  nsAutoCString query;
2674
0
  if (NS_SUCCEEDED(mTargetURI->GetQuery(query)) && !query.IsEmpty()) {
2675
0
    hasQueryString = true;
2676
0
  }
2677
0
2678
0
  MOZ_ASSERT(keysToCheck.Length() == valuesToCheck.Length());
2679
0
  for (size_t i = 0; i < keysToCheck.Length(); ++i) {
2680
0
    const char *key = keysToCheck[i].BeginReading();
2681
0
    const char *value = valuesToCheck[i].BeginReading();
2682
0
    nsCString uri;
2683
0
    uint32_t hitCount, lastHit, flags;
2684
0
2685
0
    if (!mPredictor->ParseMetaDataEntry(key, value, uri, hitCount, lastHit,
2686
0
                                        flags)) {
2687
0
      PREDICTOR_LOG(("    failed to parse key=%s value=%s", key, value));
2688
0
      continue;
2689
0
    }
2690
0
2691
0
    if (strTargetURI.Equals(uri)) {
2692
0
      bool prefetchable = true;
2693
0
      PrefetchDecisionReason reason = PREFETCHABLE;
2694
0
2695
0
      if (mHttpStatus != 200) {
2696
0
        prefetchable = false;
2697
0
        reason = STATUS_NOT_200;
2698
0
      } else if (!mMethod.EqualsLiteral("GET")) {
2699
0
        prefetchable = false;
2700
0
        reason = METHOD_NOT_GET;
2701
0
      } else if (hasQueryString) {
2702
0
        prefetchable = false;
2703
0
        reason = URL_HAS_QUERY_STRING;
2704
0
      } else if (mIsTracking) {
2705
0
        prefetchable = false;
2706
0
        reason = RESOURCE_IS_TRACKING;
2707
0
      } else if (mCouldVary) {
2708
0
        prefetchable = false;
2709
0
        reason = RESOURCE_COULD_VARY;
2710
0
      } else if (mIsNoStore) {
2711
0
        // We don't set prefetchable = false yet, because we just want to know
2712
0
        // what kind of effect this would have on prefetching.
2713
0
        reason = RESOURCE_IS_NO_STORE;
2714
0
      }
2715
0
2716
0
      Telemetry::Accumulate(Telemetry::PREDICTOR_PREFETCH_DECISION_REASON,
2717
0
                            reason);
2718
0
2719
0
      if (prefetchable) {
2720
0
        PREDICTOR_LOG(("    marking %s cacheable", key));
2721
0
        flags |= FLAG_PREFETCHABLE;
2722
0
      } else {
2723
0
        PREDICTOR_LOG(("    marking %s uncacheable", key));
2724
0
        flags &= ~FLAG_PREFETCHABLE;
2725
0
      }
2726
0
      nsCString newValue;
2727
0
      MakeMetadataEntry(hitCount, lastHit, flags, newValue);
2728
0
      entry->SetMetaDataElement(key, newValue.BeginReading());
2729
0
      break;
2730
0
    }
2731
0
  }
2732
0
2733
0
  return NS_OK;
2734
0
}
2735
2736
NS_IMETHODIMP
2737
Predictor::CacheabilityAction::OnMetaDataElement(const char *asciiKey,
2738
                                                 const char *asciiValue)
2739
0
{
2740
0
  MOZ_ASSERT(NS_IsMainThread());
2741
0
2742
0
  if (!IsURIMetadataElement(asciiKey)) {
2743
0
    return NS_OK;
2744
0
  }
2745
0
2746
0
  nsCString key, value;
2747
0
  key.AssignASCII(asciiKey);
2748
0
  value.AssignASCII(asciiValue);
2749
0
  mKeysToCheck.AppendElement(key);
2750
0
  mValuesToCheck.AppendElement(value);
2751
0
2752
0
  return NS_OK;
2753
0
}
2754
2755
} // namespace net
2756
} // namespace mozilla