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