netwerk/base/src/Seer.cpp

Thu, 22 Jan 2015 13:21:57 +0100

author
Michael Schloh von Bennewitz <michael@schloh.com>
date
Thu, 22 Jan 2015 13:21:57 +0100
branch
TOR_BUG_9701
changeset 15
b8a032363ba2
permissions
-rw-r--r--

Incorporate requested changes from Mozilla in review:
https://bugzilla.mozilla.org/show_bug.cgi?id=1123480#c6

michael@0 1 /* vim: set ts=2 sts=2 et sw=2: */
michael@0 2 /* This Source Code Form is subject to the terms of the Mozilla Public
michael@0 3 * License, v. 2.0. If a copy of the MPL was not distributed with this
michael@0 4 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
michael@0 5
michael@0 6 #include <algorithm>
michael@0 7
michael@0 8 #include "Seer.h"
michael@0 9
michael@0 10 #include "nsAppDirectoryServiceDefs.h"
michael@0 11 #include "nsICancelable.h"
michael@0 12 #include "nsIChannel.h"
michael@0 13 #include "nsIDNSListener.h"
michael@0 14 #include "nsIDNSService.h"
michael@0 15 #include "nsIDocument.h"
michael@0 16 #include "nsIFile.h"
michael@0 17 #include "nsILoadContext.h"
michael@0 18 #include "nsILoadGroup.h"
michael@0 19 #include "nsINetworkSeerVerifier.h"
michael@0 20 #include "nsIObserverService.h"
michael@0 21 #include "nsIPrefBranch.h"
michael@0 22 #include "nsIPrefService.h"
michael@0 23 #include "nsISpeculativeConnect.h"
michael@0 24 #include "nsITimer.h"
michael@0 25 #include "nsIURI.h"
michael@0 26 #include "nsNetUtil.h"
michael@0 27 #include "nsServiceManagerUtils.h"
michael@0 28 #include "nsTArray.h"
michael@0 29 #include "nsThreadUtils.h"
michael@0 30 #ifdef MOZ_NUWA_PROCESS
michael@0 31 #include "ipc/Nuwa.h"
michael@0 32 #endif
michael@0 33 #include "prlog.h"
michael@0 34
michael@0 35 #include "mozIStorageConnection.h"
michael@0 36 #include "mozIStorageService.h"
michael@0 37 #include "mozIStorageStatement.h"
michael@0 38 #include "mozStorageHelper.h"
michael@0 39
michael@0 40 #include "mozilla/Preferences.h"
michael@0 41 #include "mozilla/storage.h"
michael@0 42 #include "mozilla/Telemetry.h"
michael@0 43
michael@0 44 #if defined(ANDROID) && !defined(MOZ_WIDGET_GONK)
michael@0 45 #include "nsIPropertyBag2.h"
michael@0 46 static const int32_t ANDROID_23_VERSION = 10;
michael@0 47 #endif
michael@0 48
michael@0 49 using namespace mozilla;
michael@0 50
michael@0 51 namespace mozilla {
michael@0 52 namespace net {
michael@0 53
michael@0 54 #define RETURN_IF_FAILED(_rv) \
michael@0 55 do { \
michael@0 56 if (NS_FAILED(_rv)) { \
michael@0 57 return; \
michael@0 58 } \
michael@0 59 } while (0)
michael@0 60
michael@0 61 const char SEER_ENABLED_PREF[] = "network.seer.enabled";
michael@0 62 const char SEER_SSL_HOVER_PREF[] = "network.seer.enable-hover-on-ssl";
michael@0 63
michael@0 64 const char SEER_PAGE_DELTA_DAY_PREF[] = "network.seer.page-degradation.day";
michael@0 65 const int SEER_PAGE_DELTA_DAY_DEFAULT = 0;
michael@0 66 const char SEER_PAGE_DELTA_WEEK_PREF[] = "network.seer.page-degradation.week";
michael@0 67 const int SEER_PAGE_DELTA_WEEK_DEFAULT = 5;
michael@0 68 const char SEER_PAGE_DELTA_MONTH_PREF[] = "network.seer.page-degradation.month";
michael@0 69 const int SEER_PAGE_DELTA_MONTH_DEFAULT = 10;
michael@0 70 const char SEER_PAGE_DELTA_YEAR_PREF[] = "network.seer.page-degradation.year";
michael@0 71 const int SEER_PAGE_DELTA_YEAR_DEFAULT = 25;
michael@0 72 const char SEER_PAGE_DELTA_MAX_PREF[] = "network.seer.page-degradation.max";
michael@0 73 const int SEER_PAGE_DELTA_MAX_DEFAULT = 50;
michael@0 74 const char SEER_SUB_DELTA_DAY_PREF[] =
michael@0 75 "network.seer.subresource-degradation.day";
michael@0 76 const int SEER_SUB_DELTA_DAY_DEFAULT = 1;
michael@0 77 const char SEER_SUB_DELTA_WEEK_PREF[] =
michael@0 78 "network.seer.subresource-degradation.week";
michael@0 79 const int SEER_SUB_DELTA_WEEK_DEFAULT = 10;
michael@0 80 const char SEER_SUB_DELTA_MONTH_PREF[] =
michael@0 81 "network.seer.subresource-degradation.month";
michael@0 82 const int SEER_SUB_DELTA_MONTH_DEFAULT = 25;
michael@0 83 const char SEER_SUB_DELTA_YEAR_PREF[] =
michael@0 84 "network.seer.subresource-degradation.year";
michael@0 85 const int SEER_SUB_DELTA_YEAR_DEFAULT = 50;
michael@0 86 const char SEER_SUB_DELTA_MAX_PREF[] =
michael@0 87 "network.seer.subresource-degradation.max";
michael@0 88 const int SEER_SUB_DELTA_MAX_DEFAULT = 100;
michael@0 89
michael@0 90 const char SEER_PRECONNECT_MIN_PREF[] =
michael@0 91 "network.seer.preconnect-min-confidence";
michael@0 92 const int PRECONNECT_MIN_DEFAULT = 90;
michael@0 93 const char SEER_PRERESOLVE_MIN_PREF[] =
michael@0 94 "network.seer.preresolve-min-confidence";
michael@0 95 const int PRERESOLVE_MIN_DEFAULT = 60;
michael@0 96 const char SEER_REDIRECT_LIKELY_PREF[] =
michael@0 97 "network.seer.redirect-likely-confidence";
michael@0 98 const int REDIRECT_LIKELY_DEFAULT = 75;
michael@0 99
michael@0 100 const char SEER_MAX_QUEUE_SIZE_PREF[] = "network.seer.max-queue-size";
michael@0 101 const uint32_t SEER_MAX_QUEUE_SIZE_DEFAULT = 50;
michael@0 102
michael@0 103 const char SEER_MAX_DB_SIZE_PREF[] = "network.seer.max-db-size";
michael@0 104 const int32_t SEER_MAX_DB_SIZE_DEFAULT_BYTES = 150 * 1024 * 1024;
michael@0 105 const char SEER_PRESERVE_PERCENTAGE_PREF[] = "network.seer.preserve";
michael@0 106 const int32_t SEER_PRESERVE_PERCENTAGE_DEFAULT = 80;
michael@0 107
michael@0 108 // All these time values are in usec
michael@0 109 const long long ONE_DAY = 86400LL * 1000000LL;
michael@0 110 const long long ONE_WEEK = 7LL * ONE_DAY;
michael@0 111 const long long ONE_MONTH = 30LL * ONE_DAY;
michael@0 112 const long long ONE_YEAR = 365LL * ONE_DAY;
michael@0 113
michael@0 114 const long STARTUP_WINDOW = 5L * 60L * 1000000L; // 5min
michael@0 115
michael@0 116 // Version for the database schema
michael@0 117 static const int32_t SEER_SCHEMA_VERSION = 1;
michael@0 118
michael@0 119 struct SeerTelemetryAccumulators {
michael@0 120 Telemetry::AutoCounter<Telemetry::SEER_PREDICT_ATTEMPTS> mPredictAttempts;
michael@0 121 Telemetry::AutoCounter<Telemetry::SEER_LEARN_ATTEMPTS> mLearnAttempts;
michael@0 122 Telemetry::AutoCounter<Telemetry::SEER_PREDICT_FULL_QUEUE> mPredictFullQueue;
michael@0 123 Telemetry::AutoCounter<Telemetry::SEER_LEARN_FULL_QUEUE> mLearnFullQueue;
michael@0 124 Telemetry::AutoCounter<Telemetry::SEER_TOTAL_PREDICTIONS> mTotalPredictions;
michael@0 125 Telemetry::AutoCounter<Telemetry::SEER_TOTAL_PRECONNECTS> mTotalPreconnects;
michael@0 126 Telemetry::AutoCounter<Telemetry::SEER_TOTAL_PRERESOLVES> mTotalPreresolves;
michael@0 127 Telemetry::AutoCounter<Telemetry::SEER_PREDICTIONS_CALCULATED> mPredictionsCalculated;
michael@0 128 Telemetry::AutoCounter<Telemetry::SEER_LOAD_COUNT_IS_ZERO> mLoadCountZeroes;
michael@0 129 Telemetry::AutoCounter<Telemetry::SEER_LOAD_COUNT_OVERFLOWS> mLoadCountOverflows;
michael@0 130 Telemetry::AutoCounter<Telemetry::SEER_STARTUP_COUNT_IS_ZERO> mStartupCountZeroes;
michael@0 131 Telemetry::AutoCounter<Telemetry::SEER_STARTUP_COUNT_OVERFLOWS> mStartupCountOverflows;
michael@0 132 };
michael@0 133
michael@0 134 // Listener for the speculative DNS requests we'll fire off, which just ignores
michael@0 135 // the result (since we're just trying to warm the cache). This also exists to
michael@0 136 // reduce round-trips to the main thread, by being something threadsafe the Seer
michael@0 137 // can use.
michael@0 138
michael@0 139 class SeerDNSListener : public nsIDNSListener
michael@0 140 {
michael@0 141 public:
michael@0 142 NS_DECL_THREADSAFE_ISUPPORTS
michael@0 143 NS_DECL_NSIDNSLISTENER
michael@0 144
michael@0 145 SeerDNSListener()
michael@0 146 { }
michael@0 147
michael@0 148 virtual ~SeerDNSListener()
michael@0 149 { }
michael@0 150 };
michael@0 151
michael@0 152 NS_IMPL_ISUPPORTS(SeerDNSListener, nsIDNSListener);
michael@0 153
michael@0 154 NS_IMETHODIMP
michael@0 155 SeerDNSListener::OnLookupComplete(nsICancelable *request,
michael@0 156 nsIDNSRecord *rec,
michael@0 157 nsresult status)
michael@0 158 {
michael@0 159 return NS_OK;
michael@0 160 }
michael@0 161
michael@0 162 // Are you ready for the fun part? Because here comes the fun part. The seer,
michael@0 163 // which will do awesome stuff as you browse to make your browsing experience
michael@0 164 // faster.
michael@0 165
michael@0 166 static Seer *gSeer = nullptr;
michael@0 167
michael@0 168 #if defined(PR_LOGGING)
michael@0 169 static PRLogModuleInfo *gSeerLog = nullptr;
michael@0 170 #define SEER_LOG(args) PR_LOG(gSeerLog, 4, args)
michael@0 171 #else
michael@0 172 #define SEER_LOG(args)
michael@0 173 #endif
michael@0 174
michael@0 175 NS_IMPL_ISUPPORTS(Seer,
michael@0 176 nsINetworkSeer,
michael@0 177 nsIObserver,
michael@0 178 nsISpeculativeConnectionOverrider,
michael@0 179 nsIInterfaceRequestor)
michael@0 180
michael@0 181 Seer::Seer()
michael@0 182 :mInitialized(false)
michael@0 183 ,mEnabled(true)
michael@0 184 ,mEnableHoverOnSSL(false)
michael@0 185 ,mPageDegradationDay(SEER_PAGE_DELTA_DAY_DEFAULT)
michael@0 186 ,mPageDegradationWeek(SEER_PAGE_DELTA_WEEK_DEFAULT)
michael@0 187 ,mPageDegradationMonth(SEER_PAGE_DELTA_MONTH_DEFAULT)
michael@0 188 ,mPageDegradationYear(SEER_PAGE_DELTA_YEAR_DEFAULT)
michael@0 189 ,mPageDegradationMax(SEER_PAGE_DELTA_MAX_DEFAULT)
michael@0 190 ,mSubresourceDegradationDay(SEER_SUB_DELTA_DAY_DEFAULT)
michael@0 191 ,mSubresourceDegradationWeek(SEER_SUB_DELTA_WEEK_DEFAULT)
michael@0 192 ,mSubresourceDegradationMonth(SEER_SUB_DELTA_MONTH_DEFAULT)
michael@0 193 ,mSubresourceDegradationYear(SEER_SUB_DELTA_YEAR_DEFAULT)
michael@0 194 ,mSubresourceDegradationMax(SEER_SUB_DELTA_MAX_DEFAULT)
michael@0 195 ,mPreconnectMinConfidence(PRECONNECT_MIN_DEFAULT)
michael@0 196 ,mPreresolveMinConfidence(PRERESOLVE_MIN_DEFAULT)
michael@0 197 ,mRedirectLikelyConfidence(REDIRECT_LIKELY_DEFAULT)
michael@0 198 ,mMaxQueueSize(SEER_MAX_QUEUE_SIZE_DEFAULT)
michael@0 199 ,mStatements(mDB)
michael@0 200 ,mLastStartupTime(0)
michael@0 201 ,mStartupCount(0)
michael@0 202 ,mQueueSize(0)
michael@0 203 ,mQueueSizeLock("Seer.mQueueSizeLock")
michael@0 204 ,mCleanupScheduled(false)
michael@0 205 ,mMaxDBSize(SEER_MAX_DB_SIZE_DEFAULT_BYTES)
michael@0 206 ,mPreservePercentage(SEER_PRESERVE_PERCENTAGE_DEFAULT)
michael@0 207 ,mLastCleanupTime(0)
michael@0 208 {
michael@0 209 #if defined(PR_LOGGING)
michael@0 210 gSeerLog = PR_NewLogModule("NetworkSeer");
michael@0 211 #endif
michael@0 212
michael@0 213 MOZ_ASSERT(!gSeer, "multiple Seer instances!");
michael@0 214 gSeer = this;
michael@0 215 }
michael@0 216
michael@0 217 Seer::~Seer()
michael@0 218 {
michael@0 219 if (mInitialized)
michael@0 220 Shutdown();
michael@0 221
michael@0 222 RemoveObserver();
michael@0 223
michael@0 224 gSeer = nullptr;
michael@0 225 }
michael@0 226
michael@0 227 // Seer::nsIObserver
michael@0 228
michael@0 229 nsresult
michael@0 230 Seer::InstallObserver()
michael@0 231 {
michael@0 232 MOZ_ASSERT(NS_IsMainThread(), "Installing observer off main thread");
michael@0 233
michael@0 234 nsresult rv = NS_OK;
michael@0 235 nsCOMPtr<nsIObserverService> obs =
michael@0 236 mozilla::services::GetObserverService();
michael@0 237 if (!obs) {
michael@0 238 return NS_ERROR_NOT_AVAILABLE;
michael@0 239 }
michael@0 240
michael@0 241 rv = obs->AddObserver(this, NS_XPCOM_SHUTDOWN_OBSERVER_ID, false);
michael@0 242 NS_ENSURE_SUCCESS(rv, rv);
michael@0 243
michael@0 244 nsCOMPtr<nsIPrefBranch> prefs = do_GetService(NS_PREFSERVICE_CONTRACTID);
michael@0 245 if (!prefs) {
michael@0 246 return NS_ERROR_NOT_AVAILABLE;
michael@0 247 }
michael@0 248
michael@0 249 Preferences::AddBoolVarCache(&mEnabled, SEER_ENABLED_PREF, true);
michael@0 250 Preferences::AddBoolVarCache(&mEnableHoverOnSSL, SEER_SSL_HOVER_PREF, false);
michael@0 251 Preferences::AddIntVarCache(&mPageDegradationDay, SEER_PAGE_DELTA_DAY_PREF,
michael@0 252 SEER_PAGE_DELTA_DAY_DEFAULT);
michael@0 253 Preferences::AddIntVarCache(&mPageDegradationWeek, SEER_PAGE_DELTA_WEEK_PREF,
michael@0 254 SEER_PAGE_DELTA_WEEK_DEFAULT);
michael@0 255 Preferences::AddIntVarCache(&mPageDegradationMonth,
michael@0 256 SEER_PAGE_DELTA_MONTH_PREF,
michael@0 257 SEER_PAGE_DELTA_MONTH_DEFAULT);
michael@0 258 Preferences::AddIntVarCache(&mPageDegradationYear, SEER_PAGE_DELTA_YEAR_PREF,
michael@0 259 SEER_PAGE_DELTA_YEAR_DEFAULT);
michael@0 260 Preferences::AddIntVarCache(&mPageDegradationMax, SEER_PAGE_DELTA_MAX_PREF,
michael@0 261 SEER_PAGE_DELTA_MAX_DEFAULT);
michael@0 262
michael@0 263 Preferences::AddIntVarCache(&mSubresourceDegradationDay,
michael@0 264 SEER_SUB_DELTA_DAY_PREF,
michael@0 265 SEER_SUB_DELTA_DAY_DEFAULT);
michael@0 266 Preferences::AddIntVarCache(&mSubresourceDegradationWeek,
michael@0 267 SEER_SUB_DELTA_WEEK_PREF,
michael@0 268 SEER_SUB_DELTA_WEEK_DEFAULT);
michael@0 269 Preferences::AddIntVarCache(&mSubresourceDegradationMonth,
michael@0 270 SEER_SUB_DELTA_MONTH_PREF,
michael@0 271 SEER_SUB_DELTA_MONTH_DEFAULT);
michael@0 272 Preferences::AddIntVarCache(&mSubresourceDegradationYear,
michael@0 273 SEER_SUB_DELTA_YEAR_PREF,
michael@0 274 SEER_SUB_DELTA_YEAR_DEFAULT);
michael@0 275 Preferences::AddIntVarCache(&mSubresourceDegradationMax,
michael@0 276 SEER_SUB_DELTA_MAX_PREF,
michael@0 277 SEER_SUB_DELTA_MAX_DEFAULT);
michael@0 278
michael@0 279 Preferences::AddIntVarCache(&mPreconnectMinConfidence,
michael@0 280 SEER_PRECONNECT_MIN_PREF,
michael@0 281 PRECONNECT_MIN_DEFAULT);
michael@0 282 Preferences::AddIntVarCache(&mPreresolveMinConfidence,
michael@0 283 SEER_PRERESOLVE_MIN_PREF,
michael@0 284 PRERESOLVE_MIN_DEFAULT);
michael@0 285 Preferences::AddIntVarCache(&mRedirectLikelyConfidence,
michael@0 286 SEER_REDIRECT_LIKELY_PREF,
michael@0 287 REDIRECT_LIKELY_DEFAULT);
michael@0 288
michael@0 289 Preferences::AddIntVarCache(&mMaxQueueSize, SEER_MAX_QUEUE_SIZE_PREF,
michael@0 290 SEER_MAX_QUEUE_SIZE_DEFAULT);
michael@0 291
michael@0 292 Preferences::AddIntVarCache(&mMaxDBSize, SEER_MAX_DB_SIZE_PREF,
michael@0 293 SEER_MAX_DB_SIZE_DEFAULT_BYTES);
michael@0 294 Preferences::AddIntVarCache(&mPreservePercentage,
michael@0 295 SEER_PRESERVE_PERCENTAGE_PREF,
michael@0 296 SEER_PRESERVE_PERCENTAGE_DEFAULT);
michael@0 297
michael@0 298 return rv;
michael@0 299 }
michael@0 300
michael@0 301 void
michael@0 302 Seer::RemoveObserver()
michael@0 303 {
michael@0 304 MOZ_ASSERT(NS_IsMainThread(), "Removing observer off main thread");
michael@0 305
michael@0 306 nsCOMPtr<nsIObserverService> obs =
michael@0 307 mozilla::services::GetObserverService();
michael@0 308 if (obs) {
michael@0 309 obs->RemoveObserver(this, NS_XPCOM_SHUTDOWN_OBSERVER_ID);
michael@0 310 }
michael@0 311 }
michael@0 312
michael@0 313 static const uint32_t COMMIT_TIMER_DELTA_MS = 5 * 1000;
michael@0 314
michael@0 315 class SeerCommitTimerInitEvent : public nsRunnable
michael@0 316 {
michael@0 317 public:
michael@0 318 NS_IMETHOD Run() MOZ_OVERRIDE
michael@0 319 {
michael@0 320 nsresult rv = NS_OK;
michael@0 321
michael@0 322 if (!gSeer->mCommitTimer) {
michael@0 323 gSeer->mCommitTimer = do_CreateInstance(NS_TIMER_CONTRACTID, &rv);
michael@0 324 } else {
michael@0 325 gSeer->mCommitTimer->Cancel();
michael@0 326 }
michael@0 327 if (NS_SUCCEEDED(rv)) {
michael@0 328 gSeer->mCommitTimer->Init(gSeer, COMMIT_TIMER_DELTA_MS,
michael@0 329 nsITimer::TYPE_ONE_SHOT);
michael@0 330 }
michael@0 331
michael@0 332 return NS_OK;
michael@0 333 }
michael@0 334 };
michael@0 335
michael@0 336 class SeerNewTransactionEvent : public nsRunnable
michael@0 337 {
michael@0 338 NS_IMETHODIMP Run() MOZ_OVERRIDE
michael@0 339 {
michael@0 340 gSeer->CommitTransaction();
michael@0 341 gSeer->BeginTransaction();
michael@0 342 gSeer->MaybeScheduleCleanup();
michael@0 343 nsRefPtr<SeerCommitTimerInitEvent> event = new SeerCommitTimerInitEvent();
michael@0 344 NS_DispatchToMainThread(event);
michael@0 345 return NS_OK;
michael@0 346 }
michael@0 347 };
michael@0 348
michael@0 349 NS_IMETHODIMP
michael@0 350 Seer::Observe(nsISupports *subject, const char *topic,
michael@0 351 const char16_t *data_unicode)
michael@0 352 {
michael@0 353 nsresult rv = NS_OK;
michael@0 354 MOZ_ASSERT(NS_IsMainThread(), "Seer observing something off main thread!");
michael@0 355
michael@0 356 if (!strcmp(NS_XPCOM_SHUTDOWN_OBSERVER_ID, topic)) {
michael@0 357 Shutdown();
michael@0 358 } else if (!strcmp(NS_TIMER_CALLBACK_TOPIC, topic)) {
michael@0 359 if (mInitialized) { // Can't access io thread if we're not initialized!
michael@0 360 nsRefPtr<SeerNewTransactionEvent> event = new SeerNewTransactionEvent();
michael@0 361 mIOThread->Dispatch(event, NS_DISPATCH_NORMAL);
michael@0 362 }
michael@0 363 }
michael@0 364
michael@0 365 return rv;
michael@0 366 }
michael@0 367
michael@0 368 // Seer::nsISpeculativeConnectionOverrider
michael@0 369
michael@0 370 NS_IMETHODIMP
michael@0 371 Seer::GetIgnoreIdle(bool *ignoreIdle)
michael@0 372 {
michael@0 373 *ignoreIdle = true;
michael@0 374 return NS_OK;
michael@0 375 }
michael@0 376
michael@0 377 NS_IMETHODIMP
michael@0 378 Seer::GetIgnorePossibleSpdyConnections(bool *ignorePossibleSpdyConnections)
michael@0 379 {
michael@0 380 *ignorePossibleSpdyConnections = true;
michael@0 381 return NS_OK;
michael@0 382 }
michael@0 383
michael@0 384 NS_IMETHODIMP
michael@0 385 Seer::GetParallelSpeculativeConnectLimit(
michael@0 386 uint32_t *parallelSpeculativeConnectLimit)
michael@0 387 {
michael@0 388 *parallelSpeculativeConnectLimit = 6;
michael@0 389 return NS_OK;
michael@0 390 }
michael@0 391
michael@0 392 // Seer::nsIInterfaceRequestor
michael@0 393
michael@0 394 NS_IMETHODIMP
michael@0 395 Seer::GetInterface(const nsIID &iid, void **result)
michael@0 396 {
michael@0 397 return QueryInterface(iid, result);
michael@0 398 }
michael@0 399
michael@0 400 #ifdef MOZ_NUWA_PROCESS
michael@0 401 class NuwaMarkSeerThreadRunner : public nsRunnable
michael@0 402 {
michael@0 403 NS_IMETHODIMP Run() MOZ_OVERRIDE
michael@0 404 {
michael@0 405 if (IsNuwaProcess()) {
michael@0 406 NS_ASSERTION(NuwaMarkCurrentThread != nullptr,
michael@0 407 "NuwaMarkCurrentThread is undefined!");
michael@0 408 NuwaMarkCurrentThread(nullptr, nullptr);
michael@0 409 }
michael@0 410 return NS_OK;
michael@0 411 }
michael@0 412 };
michael@0 413 #endif
michael@0 414
michael@0 415 // Seer::nsINetworkSeer
michael@0 416
michael@0 417 nsresult
michael@0 418 Seer::Init()
michael@0 419 {
michael@0 420 if (!NS_IsMainThread()) {
michael@0 421 MOZ_ASSERT(false, "Seer::Init called off the main thread!");
michael@0 422 return NS_ERROR_UNEXPECTED;
michael@0 423 }
michael@0 424
michael@0 425 nsresult rv = NS_OK;
michael@0 426
michael@0 427 #if defined(ANDROID) && !defined(MOZ_WIDGET_GONK)
michael@0 428 // This is an ugly hack to disable the seer on android < 2.3, as it doesn't
michael@0 429 // play nicely with those android versions, at least on our infra. Causes
michael@0 430 // timeouts in reftests. See bug 881804 comment 86.
michael@0 431 nsCOMPtr<nsIPropertyBag2> infoService =
michael@0 432 do_GetService("@mozilla.org/system-info;1");
michael@0 433 if (infoService) {
michael@0 434 int32_t androidVersion = -1;
michael@0 435 rv = infoService->GetPropertyAsInt32(NS_LITERAL_STRING("version"),
michael@0 436 &androidVersion);
michael@0 437 if (NS_SUCCEEDED(rv) && (androidVersion < ANDROID_23_VERSION)) {
michael@0 438 return NS_ERROR_NOT_AVAILABLE;
michael@0 439 }
michael@0 440 }
michael@0 441 #endif
michael@0 442
michael@0 443 mStartupTime = PR_Now();
michael@0 444
michael@0 445 mAccumulators = new SeerTelemetryAccumulators();
michael@0 446
michael@0 447 rv = InstallObserver();
michael@0 448 NS_ENSURE_SUCCESS(rv, rv);
michael@0 449
michael@0 450 if (!mDNSListener) {
michael@0 451 mDNSListener = new SeerDNSListener();
michael@0 452 }
michael@0 453
michael@0 454 rv = NS_NewNamedThread("Network Seer", getter_AddRefs(mIOThread));
michael@0 455 NS_ENSURE_SUCCESS(rv, rv);
michael@0 456
michael@0 457 #ifdef MOZ_NUWA_PROCESS
michael@0 458 nsCOMPtr<nsIRunnable> runner = new NuwaMarkSeerThreadRunner();
michael@0 459 mIOThread->Dispatch(runner, NS_DISPATCH_NORMAL);
michael@0 460 #endif
michael@0 461
michael@0 462 mSpeculativeService = do_GetService("@mozilla.org/network/io-service;1", &rv);
michael@0 463 NS_ENSURE_SUCCESS(rv, rv);
michael@0 464
michael@0 465 mDnsService = do_GetService("@mozilla.org/network/dns-service;1", &rv);
michael@0 466 NS_ENSURE_SUCCESS(rv, rv);
michael@0 467
michael@0 468 mStorageService = do_GetService("@mozilla.org/storage/service;1", &rv);
michael@0 469 NS_ENSURE_SUCCESS(rv, rv);
michael@0 470
michael@0 471 rv = NS_GetSpecialDirectory(NS_APP_USER_PROFILE_50_DIR,
michael@0 472 getter_AddRefs(mDBFile));
michael@0 473 NS_ENSURE_SUCCESS(rv, rv);
michael@0 474 rv = mDBFile->AppendNative(NS_LITERAL_CSTRING("netpredictions.sqlite"));
michael@0 475 NS_ENSURE_SUCCESS(rv, rv);
michael@0 476
michael@0 477 mInitialized = true;
michael@0 478
michael@0 479 return rv;
michael@0 480 }
michael@0 481
michael@0 482 void
michael@0 483 Seer::CheckForAndDeleteOldDBFile()
michael@0 484 {
michael@0 485 nsCOMPtr<nsIFile> oldDBFile;
michael@0 486 nsresult rv = mDBFile->GetParent(getter_AddRefs(oldDBFile));
michael@0 487 RETURN_IF_FAILED(rv);
michael@0 488
michael@0 489 rv = oldDBFile->AppendNative(NS_LITERAL_CSTRING("seer.sqlite"));
michael@0 490 RETURN_IF_FAILED(rv);
michael@0 491
michael@0 492 bool oldFileExists = false;
michael@0 493 rv = oldDBFile->Exists(&oldFileExists);
michael@0 494 if (NS_FAILED(rv) || !oldFileExists) {
michael@0 495 return;
michael@0 496 }
michael@0 497
michael@0 498 oldDBFile->Remove(false);
michael@0 499 }
michael@0 500
michael@0 501 // Make sure that our sqlite storage is all set up with all the tables we need
michael@0 502 // to do the work. It isn't the end of the world if this fails, since this is
michael@0 503 // all an optimization, anyway.
michael@0 504
michael@0 505 nsresult
michael@0 506 Seer::EnsureInitStorage()
michael@0 507 {
michael@0 508 MOZ_ASSERT(!NS_IsMainThread(), "Initializing seer storage on main thread");
michael@0 509
michael@0 510 if (mDB) {
michael@0 511 return NS_OK;
michael@0 512 }
michael@0 513
michael@0 514 nsresult rv;
michael@0 515
michael@0 516 CheckForAndDeleteOldDBFile();
michael@0 517
michael@0 518 rv = mStorageService->OpenDatabase(mDBFile, getter_AddRefs(mDB));
michael@0 519 if (NS_FAILED(rv)) {
michael@0 520 // Retry once by trashing the file and trying to open again. If this fails,
michael@0 521 // we can just bail, and hope for better luck next time.
michael@0 522 rv = mDBFile->Remove(false);
michael@0 523 NS_ENSURE_SUCCESS(rv, rv);
michael@0 524
michael@0 525 rv = mStorageService->OpenDatabase(mDBFile, getter_AddRefs(mDB));
michael@0 526 NS_ENSURE_SUCCESS(rv, rv);
michael@0 527 }
michael@0 528
michael@0 529 mDB->ExecuteSimpleSQL(NS_LITERAL_CSTRING("PRAGMA synchronous = OFF;"));
michael@0 530 mDB->ExecuteSimpleSQL(NS_LITERAL_CSTRING("PRAGMA foreign_keys = ON;"));
michael@0 531
michael@0 532 BeginTransaction();
michael@0 533
michael@0 534 // A table to make sure we're working with the database layout we expect
michael@0 535 rv = mDB->ExecuteSimpleSQL(
michael@0 536 NS_LITERAL_CSTRING("CREATE TABLE IF NOT EXISTS moz_seer_version (\n"
michael@0 537 " version INTEGER NOT NULL\n"
michael@0 538 ");\n"));
michael@0 539 NS_ENSURE_SUCCESS(rv, rv);
michael@0 540
michael@0 541 nsCOMPtr<mozIStorageStatement> stmt;
michael@0 542 rv = mDB->CreateStatement(
michael@0 543 NS_LITERAL_CSTRING("SELECT version FROM moz_seer_version;\n"),
michael@0 544 getter_AddRefs(stmt));
michael@0 545 NS_ENSURE_SUCCESS(rv, rv);
michael@0 546
michael@0 547 bool hasRows;
michael@0 548 rv = stmt->ExecuteStep(&hasRows);
michael@0 549 NS_ENSURE_SUCCESS(rv, rv);
michael@0 550 if (hasRows) {
michael@0 551 int32_t currentVersion;
michael@0 552 rv = stmt->GetInt32(0, &currentVersion);
michael@0 553 NS_ENSURE_SUCCESS(rv, rv);
michael@0 554
michael@0 555 // This is what we do while we only have one schema version. Later, we'll
michael@0 556 // have to change this to actually upgrade things as appropriate.
michael@0 557 MOZ_ASSERT(currentVersion == SEER_SCHEMA_VERSION,
michael@0 558 "Invalid seer schema version!");
michael@0 559 if (currentVersion != SEER_SCHEMA_VERSION) {
michael@0 560 return NS_ERROR_UNEXPECTED;
michael@0 561 }
michael@0 562 } else {
michael@0 563 stmt = nullptr;
michael@0 564 rv = mDB->CreateStatement(
michael@0 565 NS_LITERAL_CSTRING("INSERT INTO moz_seer_version (version) VALUES "
michael@0 566 "(:seer_version);"),
michael@0 567 getter_AddRefs(stmt));
michael@0 568 NS_ENSURE_SUCCESS(rv, rv);
michael@0 569
michael@0 570 rv = stmt->BindInt32ByName(NS_LITERAL_CSTRING("seer_version"),
michael@0 571 SEER_SCHEMA_VERSION);
michael@0 572 NS_ENSURE_SUCCESS(rv, rv);
michael@0 573
michael@0 574 stmt->Execute();
michael@0 575 }
michael@0 576
michael@0 577 stmt = nullptr;
michael@0 578
michael@0 579 // This table keeps track of the hosts we've seen at the top level of a
michael@0 580 // pageload so we can map them to hosts used for subresources of a pageload.
michael@0 581 rv = mDB->ExecuteSimpleSQL(
michael@0 582 NS_LITERAL_CSTRING("CREATE TABLE IF NOT EXISTS moz_hosts (\n"
michael@0 583 " id INTEGER PRIMARY KEY AUTOINCREMENT,\n"
michael@0 584 " origin TEXT NOT NULL,\n"
michael@0 585 " loads INTEGER DEFAULT 0,\n"
michael@0 586 " last_load INTEGER DEFAULT 0\n"
michael@0 587 ");\n"));
michael@0 588 NS_ENSURE_SUCCESS(rv, rv);
michael@0 589
michael@0 590 rv = mDB->ExecuteSimpleSQL(
michael@0 591 NS_LITERAL_CSTRING("CREATE INDEX IF NOT EXISTS host_id_origin_index "
michael@0 592 "ON moz_hosts (id, origin);"));
michael@0 593 NS_ENSURE_SUCCESS(rv, rv);
michael@0 594
michael@0 595 rv = mDB->ExecuteSimpleSQL(
michael@0 596 NS_LITERAL_CSTRING("CREATE INDEX IF NOT EXISTS host_origin_index "
michael@0 597 "ON moz_hosts (origin);"));
michael@0 598 NS_ENSURE_SUCCESS(rv, rv);
michael@0 599
michael@0 600 rv = mDB->ExecuteSimpleSQL(
michael@0 601 NS_LITERAL_CSTRING("CREATE INDEX IF NOT EXISTS host_load_index "
michael@0 602 "ON moz_hosts (last_load);"));
michael@0 603 NS_ENSURE_SUCCESS(rv, rv);
michael@0 604
michael@0 605 // And this is the table that keeps track of the hosts for subresources of a
michael@0 606 // pageload.
michael@0 607 rv = mDB->ExecuteSimpleSQL(
michael@0 608 NS_LITERAL_CSTRING("CREATE TABLE IF NOT EXISTS moz_subhosts (\n"
michael@0 609 " id INTEGER PRIMARY KEY AUTOINCREMENT,\n"
michael@0 610 " hid INTEGER NOT NULL,\n"
michael@0 611 " origin TEXT NOT NULL,\n"
michael@0 612 " hits INTEGER DEFAULT 0,\n"
michael@0 613 " last_hit INTEGER DEFAULT 0,\n"
michael@0 614 " FOREIGN KEY(hid) REFERENCES moz_hosts(id) ON DELETE CASCADE\n"
michael@0 615 ");\n"));
michael@0 616 NS_ENSURE_SUCCESS(rv, rv);
michael@0 617
michael@0 618 rv = mDB->ExecuteSimpleSQL(
michael@0 619 NS_LITERAL_CSTRING("CREATE INDEX IF NOT EXISTS subhost_hid_origin_index "
michael@0 620 "ON moz_subhosts (hid, origin);"));
michael@0 621 NS_ENSURE_SUCCESS(rv, rv);
michael@0 622
michael@0 623 rv = mDB->ExecuteSimpleSQL(
michael@0 624 NS_LITERAL_CSTRING("CREATE INDEX IF NOT EXISTS subhost_id_index "
michael@0 625 "ON moz_subhosts (id);"));
michael@0 626 NS_ENSURE_SUCCESS(rv, rv);
michael@0 627
michael@0 628 // Table to keep track of how many times we've started up, and when the last
michael@0 629 // time was.
michael@0 630 rv = mDB->ExecuteSimpleSQL(
michael@0 631 NS_LITERAL_CSTRING("CREATE TABLE IF NOT EXISTS moz_startups (\n"
michael@0 632 " startups INTEGER,\n"
michael@0 633 " last_startup INTEGER\n"
michael@0 634 ");\n"));
michael@0 635 NS_ENSURE_SUCCESS(rv, rv);
michael@0 636
michael@0 637 rv = mDB->CreateStatement(
michael@0 638 NS_LITERAL_CSTRING("SELECT startups, last_startup FROM moz_startups;\n"),
michael@0 639 getter_AddRefs(stmt));
michael@0 640 NS_ENSURE_SUCCESS(rv, rv);
michael@0 641
michael@0 642 // We'll go ahead and keep track of our startup count here, since we can
michael@0 643 // (mostly) equate "the service was created and asked to do stuff" with
michael@0 644 // "the browser was started up".
michael@0 645 rv = stmt->ExecuteStep(&hasRows);
michael@0 646 NS_ENSURE_SUCCESS(rv, rv);
michael@0 647 if (hasRows) {
michael@0 648 // We've started up before. Update our startup statistics
michael@0 649 stmt->GetInt32(0, &mStartupCount);
michael@0 650 stmt->GetInt64(1, &mLastStartupTime);
michael@0 651
michael@0 652 // This finalizes the statement
michael@0 653 stmt = nullptr;
michael@0 654
michael@0 655 rv = mDB->CreateStatement(
michael@0 656 NS_LITERAL_CSTRING("UPDATE moz_startups SET startups = :startup_count, "
michael@0 657 "last_startup = :startup_time;\n"),
michael@0 658 getter_AddRefs(stmt));
michael@0 659 NS_ENSURE_SUCCESS(rv, rv);
michael@0 660
michael@0 661 int32_t newStartupCount = mStartupCount + 1;
michael@0 662 if (newStartupCount <= 0) {
michael@0 663 SEER_LOG(("Seer::EnsureInitStorage startup count overflow\n"));
michael@0 664 newStartupCount = mStartupCount;
michael@0 665 ++mAccumulators->mStartupCountOverflows;
michael@0 666 }
michael@0 667
michael@0 668 rv = stmt->BindInt32ByName(NS_LITERAL_CSTRING("startup_count"),
michael@0 669 mStartupCount + 1);
michael@0 670 NS_ENSURE_SUCCESS(rv, rv);
michael@0 671
michael@0 672 rv = stmt->BindInt64ByName(NS_LITERAL_CSTRING("startup_time"),
michael@0 673 mStartupTime);
michael@0 674 NS_ENSURE_SUCCESS(rv, rv);
michael@0 675
michael@0 676 stmt->Execute();
michael@0 677 } else {
michael@0 678 // This is our first startup, so let's go ahead and mark it as such
michael@0 679 mStartupCount = 1;
michael@0 680
michael@0 681 rv = mDB->CreateStatement(
michael@0 682 NS_LITERAL_CSTRING("INSERT INTO moz_startups (startups, last_startup) "
michael@0 683 "VALUES (1, :startup_time);\n"),
michael@0 684 getter_AddRefs(stmt));
michael@0 685 NS_ENSURE_SUCCESS(rv, rv);
michael@0 686
michael@0 687 rv = stmt->BindInt64ByName(NS_LITERAL_CSTRING("startup_time"),
michael@0 688 mStartupTime);
michael@0 689 NS_ENSURE_SUCCESS(rv, rv);
michael@0 690
michael@0 691 stmt->Execute();
michael@0 692 }
michael@0 693
michael@0 694 // This finalizes the statement
michael@0 695 stmt = nullptr;
michael@0 696
michael@0 697 // This table lists URIs loaded at startup, along with how many startups
michael@0 698 // they've been loaded during, and when the last time was.
michael@0 699 rv = mDB->ExecuteSimpleSQL(
michael@0 700 NS_LITERAL_CSTRING("CREATE TABLE IF NOT EXISTS moz_startup_pages (\n"
michael@0 701 " id INTEGER PRIMARY KEY AUTOINCREMENT,\n"
michael@0 702 " uri TEXT NOT NULL,\n"
michael@0 703 " hits INTEGER DEFAULT 0,\n"
michael@0 704 " last_hit INTEGER DEFAULT 0\n"
michael@0 705 ");\n"));
michael@0 706 NS_ENSURE_SUCCESS(rv, rv);
michael@0 707
michael@0 708 rv = mDB->ExecuteSimpleSQL(
michael@0 709 NS_LITERAL_CSTRING("CREATE INDEX IF NOT EXISTS startup_page_uri_index "
michael@0 710 "ON moz_startup_pages (uri);"));
michael@0 711 NS_ENSURE_SUCCESS(rv, rv);
michael@0 712
michael@0 713 rv = mDB->ExecuteSimpleSQL(
michael@0 714 NS_LITERAL_CSTRING("CREATE INDEX IF NOT EXISTS startup_page_hit_index "
michael@0 715 "ON moz_startup_pages (last_hit);"));
michael@0 716 NS_ENSURE_SUCCESS(rv, rv);
michael@0 717
michael@0 718 // This table is similar to moz_hosts above, but uses full URIs instead of
michael@0 719 // hosts so that we can get more specific predictions for URIs that people
michael@0 720 // visit often (such as their email or social network home pages).
michael@0 721 rv = mDB->ExecuteSimpleSQL(
michael@0 722 NS_LITERAL_CSTRING("CREATE TABLE IF NOT EXISTS moz_pages (\n"
michael@0 723 " id integer PRIMARY KEY AUTOINCREMENT,\n"
michael@0 724 " uri TEXT NOT NULL,\n"
michael@0 725 " loads INTEGER DEFAULT 0,\n"
michael@0 726 " last_load INTEGER DEFAULT 0\n"
michael@0 727 ");\n"));
michael@0 728 NS_ENSURE_SUCCESS(rv, rv);
michael@0 729
michael@0 730 rv = mDB->ExecuteSimpleSQL(
michael@0 731 NS_LITERAL_CSTRING("CREATE INDEX IF NOT EXISTS page_id_uri_index "
michael@0 732 "ON moz_pages (id, uri);"));
michael@0 733 NS_ENSURE_SUCCESS(rv, rv);
michael@0 734
michael@0 735 rv = mDB->ExecuteSimpleSQL(
michael@0 736 NS_LITERAL_CSTRING("CREATE INDEX IF NOT EXISTS page_uri_index "
michael@0 737 "ON moz_pages (uri);"));
michael@0 738 NS_ENSURE_SUCCESS(rv, rv);
michael@0 739
michael@0 740 // This table is similar to moz_subhosts above, but is instead related to
michael@0 741 // moz_pages for finer granularity.
michael@0 742 rv = mDB->ExecuteSimpleSQL(
michael@0 743 NS_LITERAL_CSTRING("CREATE TABLE IF NOT EXISTS moz_subresources (\n"
michael@0 744 " id integer PRIMARY KEY AUTOINCREMENT,\n"
michael@0 745 " pid INTEGER NOT NULL,\n"
michael@0 746 " uri TEXT NOT NULL,\n"
michael@0 747 " hits INTEGER DEFAULT 0,\n"
michael@0 748 " last_hit INTEGER DEFAULT 0,\n"
michael@0 749 " FOREIGN KEY(pid) REFERENCES moz_pages(id) ON DELETE CASCADE\n"
michael@0 750 ");\n"));
michael@0 751 NS_ENSURE_SUCCESS(rv, rv);
michael@0 752
michael@0 753 rv = mDB->ExecuteSimpleSQL(
michael@0 754 NS_LITERAL_CSTRING("CREATE INDEX IF NOT EXISTS subresource_pid_uri_index "
michael@0 755 "ON moz_subresources (pid, uri);"));
michael@0 756 NS_ENSURE_SUCCESS(rv, rv);
michael@0 757
michael@0 758 rv = mDB->ExecuteSimpleSQL(
michael@0 759 NS_LITERAL_CSTRING("CREATE INDEX IF NOT EXISTS subresource_id_index "
michael@0 760 "ON moz_subresources (id);"));
michael@0 761 NS_ENSURE_SUCCESS(rv, rv);
michael@0 762
michael@0 763 // This table keeps track of URIs and what they end up finally redirecting to
michael@0 764 // so we can handle redirects in a sane fashion, as well.
michael@0 765 rv = mDB->ExecuteSimpleSQL(
michael@0 766 NS_LITERAL_CSTRING("CREATE TABLE IF NOT EXISTS moz_redirects (\n"
michael@0 767 " id integer PRIMARY KEY AUTOINCREMENT,\n"
michael@0 768 " pid integer NOT NULL,\n"
michael@0 769 " uri TEXT NOT NULL,\n"
michael@0 770 " origin TEXT NOT NULL,\n"
michael@0 771 " hits INTEGER DEFAULT 0,\n"
michael@0 772 " last_hit INTEGER DEFAULT 0,\n"
michael@0 773 " FOREIGN KEY(pid) REFERENCES moz_pages(id) ON DELETE CASCADE\n"
michael@0 774 ");\n"));
michael@0 775 NS_ENSURE_SUCCESS(rv, rv);
michael@0 776
michael@0 777 rv = mDB->ExecuteSimpleSQL(
michael@0 778 NS_LITERAL_CSTRING("CREATE INDEX IF NOT EXISTS redirect_pid_uri_index "
michael@0 779 "ON moz_redirects (pid, uri);"));
michael@0 780 NS_ENSURE_SUCCESS(rv, rv);
michael@0 781
michael@0 782 rv = mDB->ExecuteSimpleSQL(
michael@0 783 NS_LITERAL_CSTRING("CREATE INDEX IF NOT EXISTS redirect_id_index "
michael@0 784 "ON moz_redirects (id);"));
michael@0 785 NS_ENSURE_SUCCESS(rv, rv);
michael@0 786
michael@0 787 CommitTransaction();
michael@0 788 BeginTransaction();
michael@0 789
michael@0 790 nsRefPtr<SeerCommitTimerInitEvent> event = new SeerCommitTimerInitEvent();
michael@0 791 NS_DispatchToMainThread(event, NS_DISPATCH_NORMAL);
michael@0 792
michael@0 793 return NS_OK;
michael@0 794 }
michael@0 795
michael@0 796 class SeerThreadShutdownRunner : public nsRunnable
michael@0 797 {
michael@0 798 public:
michael@0 799 SeerThreadShutdownRunner(nsIThread *ioThread)
michael@0 800 :mIOThread(ioThread)
michael@0 801 { }
michael@0 802
michael@0 803 NS_IMETHODIMP Run() MOZ_OVERRIDE
michael@0 804 {
michael@0 805 MOZ_ASSERT(NS_IsMainThread(), "Shut down seer io thread off main thread");
michael@0 806 mIOThread->Shutdown();
michael@0 807 return NS_OK;
michael@0 808 }
michael@0 809
michael@0 810 private:
michael@0 811 nsCOMPtr<nsIThread> mIOThread;
michael@0 812 };
michael@0 813
michael@0 814 class SeerDBShutdownRunner : public nsRunnable
michael@0 815 {
michael@0 816 public:
michael@0 817 SeerDBShutdownRunner(nsIThread *ioThread, nsINetworkSeer *seer)
michael@0 818 :mIOThread(ioThread)
michael@0 819 {
michael@0 820 mSeer = new nsMainThreadPtrHolder<nsINetworkSeer>(seer);
michael@0 821 }
michael@0 822
michael@0 823 NS_IMETHODIMP Run() MOZ_OVERRIDE
michael@0 824 {
michael@0 825 MOZ_ASSERT(!NS_IsMainThread(), "Shutting down DB on main thread");
michael@0 826
michael@0 827 // Ensure everything is written to disk before we shut down the db
michael@0 828 gSeer->CommitTransaction();
michael@0 829
michael@0 830 gSeer->mStatements.FinalizeStatements();
michael@0 831 gSeer->mDB->Close();
michael@0 832 gSeer->mDB = nullptr;
michael@0 833
michael@0 834 nsRefPtr<SeerThreadShutdownRunner> runner =
michael@0 835 new SeerThreadShutdownRunner(mIOThread);
michael@0 836 NS_DispatchToMainThread(runner);
michael@0 837
michael@0 838 return NS_OK;
michael@0 839 }
michael@0 840
michael@0 841 private:
michael@0 842 nsCOMPtr<nsIThread> mIOThread;
michael@0 843
michael@0 844 // Death grip to keep seer alive while we cleanly close its DB connection
michael@0 845 nsMainThreadPtrHandle<nsINetworkSeer> mSeer;
michael@0 846 };
michael@0 847
michael@0 848 void
michael@0 849 Seer::Shutdown()
michael@0 850 {
michael@0 851 if (!NS_IsMainThread()) {
michael@0 852 MOZ_ASSERT(false, "Seer::Shutdown called off the main thread!");
michael@0 853 return;
michael@0 854 }
michael@0 855
michael@0 856 mInitialized = false;
michael@0 857
michael@0 858 if (mCommitTimer) {
michael@0 859 mCommitTimer->Cancel();
michael@0 860 }
michael@0 861
michael@0 862 if (mIOThread) {
michael@0 863 if (mDB) {
michael@0 864 nsRefPtr<SeerDBShutdownRunner> runner =
michael@0 865 new SeerDBShutdownRunner(mIOThread, this);
michael@0 866 mIOThread->Dispatch(runner, NS_DISPATCH_NORMAL);
michael@0 867 } else {
michael@0 868 nsRefPtr<SeerThreadShutdownRunner> runner =
michael@0 869 new SeerThreadShutdownRunner(mIOThread);
michael@0 870 NS_DispatchToMainThread(runner);
michael@0 871 }
michael@0 872 }
michael@0 873 }
michael@0 874
michael@0 875 nsresult
michael@0 876 Seer::Create(nsISupports *aOuter, const nsIID& aIID,
michael@0 877 void **aResult)
michael@0 878 {
michael@0 879 nsresult rv;
michael@0 880
michael@0 881 if (aOuter != nullptr) {
michael@0 882 return NS_ERROR_NO_AGGREGATION;
michael@0 883 }
michael@0 884
michael@0 885 nsRefPtr<Seer> svc = new Seer();
michael@0 886
michael@0 887 rv = svc->Init();
michael@0 888 if (NS_FAILED(rv)) {
michael@0 889 SEER_LOG(("Failed to initialize seer, seer will be a noop"));
michael@0 890 }
michael@0 891
michael@0 892 // We treat init failure the same as the service being disabled, since this
michael@0 893 // is all an optimization anyway. No need to freak people out. That's why we
michael@0 894 // gladly continue on QI'ing here.
michael@0 895 rv = svc->QueryInterface(aIID, aResult);
michael@0 896
michael@0 897 return rv;
michael@0 898 }
michael@0 899
michael@0 900 // Get the full origin (scheme, host, port) out of a URI (maybe should be part
michael@0 901 // of nsIURI instead?)
michael@0 902 static void
michael@0 903 ExtractOrigin(nsIURI *uri, nsAutoCString &s)
michael@0 904 {
michael@0 905 s.Truncate();
michael@0 906
michael@0 907 nsAutoCString scheme;
michael@0 908 nsresult rv = uri->GetScheme(scheme);
michael@0 909 RETURN_IF_FAILED(rv);
michael@0 910
michael@0 911 nsAutoCString host;
michael@0 912 rv = uri->GetAsciiHost(host);
michael@0 913 RETURN_IF_FAILED(rv);
michael@0 914
michael@0 915 int32_t port;
michael@0 916 rv = uri->GetPort(&port);
michael@0 917 RETURN_IF_FAILED(rv);
michael@0 918
michael@0 919 s.Assign(scheme);
michael@0 920 s.AppendLiteral("://");
michael@0 921 s.Append(host);
michael@0 922 if (port != -1) {
michael@0 923 s.AppendLiteral(":");
michael@0 924 s.AppendInt(port);
michael@0 925 }
michael@0 926 }
michael@0 927
michael@0 928 // An event to do the work for a prediction that needs to hit the sqlite
michael@0 929 // database. These events should be created on the main thread, and run on
michael@0 930 // the seer thread.
michael@0 931 class SeerPredictionEvent : public nsRunnable
michael@0 932 {
michael@0 933 public:
michael@0 934 SeerPredictionEvent(nsIURI *targetURI, nsIURI *sourceURI,
michael@0 935 SeerPredictReason reason,
michael@0 936 nsINetworkSeerVerifier *verifier)
michael@0 937 :mReason(reason)
michael@0 938 {
michael@0 939 MOZ_ASSERT(NS_IsMainThread(), "Creating prediction event off main thread");
michael@0 940
michael@0 941 mEnqueueTime = TimeStamp::Now();
michael@0 942
michael@0 943 if (verifier) {
michael@0 944 mVerifier = new nsMainThreadPtrHolder<nsINetworkSeerVerifier>(verifier);
michael@0 945 }
michael@0 946 if (targetURI) {
michael@0 947 targetURI->GetAsciiSpec(mTargetURI.spec);
michael@0 948 ExtractOrigin(targetURI, mTargetURI.origin);
michael@0 949 }
michael@0 950 if (sourceURI) {
michael@0 951 sourceURI->GetAsciiSpec(mSourceURI.spec);
michael@0 952 ExtractOrigin(sourceURI, mSourceURI.origin);
michael@0 953 }
michael@0 954 }
michael@0 955
michael@0 956 NS_IMETHOD Run() MOZ_OVERRIDE
michael@0 957 {
michael@0 958 MOZ_ASSERT(!NS_IsMainThread(), "Running prediction event on main thread");
michael@0 959
michael@0 960 Telemetry::AccumulateTimeDelta(Telemetry::SEER_PREDICT_WAIT_TIME,
michael@0 961 mEnqueueTime);
michael@0 962
michael@0 963 TimeStamp startTime = TimeStamp::Now();
michael@0 964
michael@0 965 nsresult rv = NS_OK;
michael@0 966
michael@0 967 switch (mReason) {
michael@0 968 case nsINetworkSeer::PREDICT_LOAD:
michael@0 969 gSeer->PredictForPageload(mTargetURI, mVerifier, 0, mEnqueueTime);
michael@0 970 break;
michael@0 971 case nsINetworkSeer::PREDICT_STARTUP:
michael@0 972 gSeer->PredictForStartup(mVerifier, mEnqueueTime);
michael@0 973 break;
michael@0 974 default:
michael@0 975 MOZ_ASSERT(false, "Got unexpected value for predict reason");
michael@0 976 rv = NS_ERROR_UNEXPECTED;
michael@0 977 }
michael@0 978
michael@0 979 gSeer->FreeSpaceInQueue();
michael@0 980
michael@0 981 Telemetry::AccumulateTimeDelta(Telemetry::SEER_PREDICT_WORK_TIME,
michael@0 982 startTime);
michael@0 983
michael@0 984 gSeer->MaybeScheduleCleanup();
michael@0 985
michael@0 986 return rv;
michael@0 987 }
michael@0 988
michael@0 989 private:
michael@0 990 Seer::UriInfo mTargetURI;
michael@0 991 Seer::UriInfo mSourceURI;
michael@0 992 SeerPredictReason mReason;
michael@0 993 SeerVerifierHandle mVerifier;
michael@0 994 TimeStamp mEnqueueTime;
michael@0 995 };
michael@0 996
michael@0 997 // Predicting for a link is easy, and doesn't require the round-trip to the
michael@0 998 // seer thread and back to the main thread, since we don't have to hit the db
michael@0 999 // for that.
michael@0 1000 void
michael@0 1001 Seer::PredictForLink(nsIURI *targetURI, nsIURI *sourceURI,
michael@0 1002 nsINetworkSeerVerifier *verifier)
michael@0 1003 {
michael@0 1004 MOZ_ASSERT(NS_IsMainThread(), "Predicting for link off main thread");
michael@0 1005
michael@0 1006 if (!mSpeculativeService) {
michael@0 1007 return;
michael@0 1008 }
michael@0 1009
michael@0 1010 if (!mEnableHoverOnSSL) {
michael@0 1011 bool isSSL = false;
michael@0 1012 sourceURI->SchemeIs("https", &isSSL);
michael@0 1013 if (isSSL) {
michael@0 1014 // We don't want to predict from an HTTPS page, to avoid info leakage
michael@0 1015 SEER_LOG(("Not predicting for link hover - on an SSL page"));
michael@0 1016 return;
michael@0 1017 }
michael@0 1018 }
michael@0 1019
michael@0 1020 mSpeculativeService->SpeculativeConnect(targetURI, nullptr);
michael@0 1021 if (verifier) {
michael@0 1022 verifier->OnPredictPreconnect(targetURI);
michael@0 1023 }
michael@0 1024 }
michael@0 1025
michael@0 1026 // This runnable runs on the main thread, and is responsible for actually
michael@0 1027 // firing off predictive actions (such as TCP/TLS preconnects and DNS lookups)
michael@0 1028 class SeerPredictionRunner : public nsRunnable
michael@0 1029 {
michael@0 1030 public:
michael@0 1031 SeerPredictionRunner(SeerVerifierHandle &verifier, TimeStamp predictStartTime)
michael@0 1032 :mVerifier(verifier)
michael@0 1033 ,mPredictStartTime(predictStartTime)
michael@0 1034 { }
michael@0 1035
michael@0 1036 void AddPreconnect(const nsACString &uri)
michael@0 1037 {
michael@0 1038 mPreconnects.AppendElement(uri);
michael@0 1039 }
michael@0 1040
michael@0 1041 void AddPreresolve(const nsACString &uri)
michael@0 1042 {
michael@0 1043 mPreresolves.AppendElement(uri);
michael@0 1044 }
michael@0 1045
michael@0 1046 bool HasWork() const
michael@0 1047 {
michael@0 1048 return !(mPreconnects.IsEmpty() && mPreresolves.IsEmpty());
michael@0 1049 }
michael@0 1050
michael@0 1051 NS_IMETHOD Run() MOZ_OVERRIDE
michael@0 1052 {
michael@0 1053 MOZ_ASSERT(NS_IsMainThread(), "Running prediction off main thread");
michael@0 1054
michael@0 1055 Telemetry::AccumulateTimeDelta(Telemetry::SEER_PREDICT_TIME_TO_ACTION,
michael@0 1056 mPredictStartTime);
michael@0 1057
michael@0 1058 uint32_t len, i;
michael@0 1059
michael@0 1060 len = mPreconnects.Length();
michael@0 1061 for (i = 0; i < len; ++i) {
michael@0 1062 nsCOMPtr<nsIURI> uri;
michael@0 1063 nsresult rv = NS_NewURI(getter_AddRefs(uri), mPreconnects[i]);
michael@0 1064 if (NS_FAILED(rv)) {
michael@0 1065 continue;
michael@0 1066 }
michael@0 1067
michael@0 1068 ++gSeer->mAccumulators->mTotalPredictions;
michael@0 1069 ++gSeer->mAccumulators->mTotalPreconnects;
michael@0 1070 gSeer->mSpeculativeService->SpeculativeConnect(uri, gSeer);
michael@0 1071 if (mVerifier) {
michael@0 1072 mVerifier->OnPredictPreconnect(uri);
michael@0 1073 }
michael@0 1074 }
michael@0 1075
michael@0 1076 len = mPreresolves.Length();
michael@0 1077 nsCOMPtr<nsIThread> mainThread = do_GetMainThread();
michael@0 1078 for (i = 0; i < len; ++i) {
michael@0 1079 nsCOMPtr<nsIURI> uri;
michael@0 1080 nsresult rv = NS_NewURI(getter_AddRefs(uri), mPreresolves[i]);
michael@0 1081 if (NS_FAILED(rv)) {
michael@0 1082 continue;
michael@0 1083 }
michael@0 1084
michael@0 1085 ++gSeer->mAccumulators->mTotalPredictions;
michael@0 1086 ++gSeer->mAccumulators->mTotalPreresolves;
michael@0 1087 nsAutoCString hostname;
michael@0 1088 uri->GetAsciiHost(hostname);
michael@0 1089 nsCOMPtr<nsICancelable> tmpCancelable;
michael@0 1090 gSeer->mDnsService->AsyncResolve(hostname,
michael@0 1091 (nsIDNSService::RESOLVE_PRIORITY_MEDIUM |
michael@0 1092 nsIDNSService::RESOLVE_SPECULATE),
michael@0 1093 gSeer->mDNSListener, nullptr,
michael@0 1094 getter_AddRefs(tmpCancelable));
michael@0 1095 if (mVerifier) {
michael@0 1096 mVerifier->OnPredictDNS(uri);
michael@0 1097 }
michael@0 1098 }
michael@0 1099
michael@0 1100 mPreconnects.Clear();
michael@0 1101 mPreresolves.Clear();
michael@0 1102
michael@0 1103 return NS_OK;
michael@0 1104 }
michael@0 1105
michael@0 1106 private:
michael@0 1107 nsTArray<nsCString> mPreconnects;
michael@0 1108 nsTArray<nsCString> mPreresolves;
michael@0 1109 SeerVerifierHandle mVerifier;
michael@0 1110 TimeStamp mPredictStartTime;
michael@0 1111 };
michael@0 1112
michael@0 1113 // This calculates how much to degrade our confidence in our data based on
michael@0 1114 // the last time this top-level resource was loaded. This "global degradation"
michael@0 1115 // applies to *all* subresources we have associated with the top-level
michael@0 1116 // resource. This will be in addition to any reduction in confidence we have
michael@0 1117 // associated with a particular subresource.
michael@0 1118 int
michael@0 1119 Seer::CalculateGlobalDegradation(PRTime now, PRTime lastLoad)
michael@0 1120 {
michael@0 1121 int globalDegradation;
michael@0 1122 PRTime delta = now - lastLoad;
michael@0 1123 if (delta < ONE_DAY) {
michael@0 1124 globalDegradation = mPageDegradationDay;
michael@0 1125 } else if (delta < ONE_WEEK) {
michael@0 1126 globalDegradation = mPageDegradationWeek;
michael@0 1127 } else if (delta < ONE_MONTH) {
michael@0 1128 globalDegradation = mPageDegradationMonth;
michael@0 1129 } else if (delta < ONE_YEAR) {
michael@0 1130 globalDegradation = mPageDegradationYear;
michael@0 1131 } else {
michael@0 1132 globalDegradation = mPageDegradationMax;
michael@0 1133 }
michael@0 1134
michael@0 1135 Telemetry::Accumulate(Telemetry::SEER_GLOBAL_DEGRADATION, globalDegradation);
michael@0 1136 return globalDegradation;
michael@0 1137 }
michael@0 1138
michael@0 1139 // This calculates our overall confidence that a particular subresource will be
michael@0 1140 // loaded as part of a top-level load.
michael@0 1141 // @param baseConfidence - the basic confidence we have for this subresource,
michael@0 1142 // which is the percentage of time this top-level load
michael@0 1143 // loads the subresource in question
michael@0 1144 // @param lastHit - the timestamp of the last time we loaded this subresource as
michael@0 1145 // part of this top-level load
michael@0 1146 // @param lastPossible - the timestamp of the last time we performed this
michael@0 1147 // top-level load
michael@0 1148 // @param globalDegradation - the degradation for this top-level load as
michael@0 1149 // determined by CalculateGlobalDegradation
michael@0 1150 int
michael@0 1151 Seer::CalculateConfidence(int baseConfidence, PRTime lastHit,
michael@0 1152 PRTime lastPossible, int globalDegradation)
michael@0 1153 {
michael@0 1154 ++mAccumulators->mPredictionsCalculated;
michael@0 1155
michael@0 1156 int maxConfidence = 100;
michael@0 1157 int confidenceDegradation = 0;
michael@0 1158
michael@0 1159 if (lastHit < lastPossible) {
michael@0 1160 // We didn't load this subresource the last time this top-level load was
michael@0 1161 // performed, so let's not bother preconnecting (at the very least).
michael@0 1162 maxConfidence = mPreconnectMinConfidence - 1;
michael@0 1163
michael@0 1164 // Now calculate how much we want to degrade our confidence based on how
michael@0 1165 // long it's been between the last time we did this top-level load and the
michael@0 1166 // last time this top-level load included this subresource.
michael@0 1167 PRTime delta = lastPossible - lastHit;
michael@0 1168 if (delta == 0) {
michael@0 1169 confidenceDegradation = 0;
michael@0 1170 } else if (delta < ONE_DAY) {
michael@0 1171 confidenceDegradation = mSubresourceDegradationDay;
michael@0 1172 } else if (delta < ONE_WEEK) {
michael@0 1173 confidenceDegradation = mSubresourceDegradationWeek;
michael@0 1174 } else if (delta < ONE_MONTH) {
michael@0 1175 confidenceDegradation = mSubresourceDegradationMonth;
michael@0 1176 } else if (delta < ONE_YEAR) {
michael@0 1177 confidenceDegradation = mSubresourceDegradationYear;
michael@0 1178 } else {
michael@0 1179 confidenceDegradation = mSubresourceDegradationMax;
michael@0 1180 maxConfidence = 0;
michael@0 1181 }
michael@0 1182 }
michael@0 1183
michael@0 1184 // Calculate our confidence and clamp it to between 0 and maxConfidence
michael@0 1185 // (<= 100)
michael@0 1186 int confidence = baseConfidence - confidenceDegradation - globalDegradation;
michael@0 1187 confidence = std::max(confidence, 0);
michael@0 1188 confidence = std::min(confidence, maxConfidence);
michael@0 1189
michael@0 1190 Telemetry::Accumulate(Telemetry::SEER_BASE_CONFIDENCE, baseConfidence);
michael@0 1191 Telemetry::Accumulate(Telemetry::SEER_SUBRESOURCE_DEGRADATION,
michael@0 1192 confidenceDegradation);
michael@0 1193 Telemetry::Accumulate(Telemetry::SEER_CONFIDENCE, confidence);
michael@0 1194 return confidence;
michael@0 1195 }
michael@0 1196
michael@0 1197 // (Maybe) adds a predictive action to the prediction runner, based on our
michael@0 1198 // calculated confidence for the subresource in question.
michael@0 1199 void
michael@0 1200 Seer::SetupPrediction(int confidence, const nsACString &uri,
michael@0 1201 SeerPredictionRunner *runner)
michael@0 1202 {
michael@0 1203 if (confidence >= mPreconnectMinConfidence) {
michael@0 1204 runner->AddPreconnect(uri);
michael@0 1205 } else if (confidence >= mPreresolveMinConfidence) {
michael@0 1206 runner->AddPreresolve(uri);
michael@0 1207 }
michael@0 1208 }
michael@0 1209
michael@0 1210 // This gets the data about the top-level load from our database, either from
michael@0 1211 // the pages table (which is specific to a particular URI), or from the hosts
michael@0 1212 // table (which is for a particular origin).
michael@0 1213 bool
michael@0 1214 Seer::LookupTopLevel(QueryType queryType, const nsACString &key,
michael@0 1215 TopLevelInfo &info)
michael@0 1216 {
michael@0 1217 MOZ_ASSERT(!NS_IsMainThread(), "LookupTopLevel called on main thread.");
michael@0 1218
michael@0 1219 nsCOMPtr<mozIStorageStatement> stmt;
michael@0 1220 if (queryType == QUERY_PAGE) {
michael@0 1221 stmt = mStatements.GetCachedStatement(
michael@0 1222 NS_LITERAL_CSTRING("SELECT id, loads, last_load FROM moz_pages WHERE "
michael@0 1223 "uri = :key;"));
michael@0 1224 } else {
michael@0 1225 stmt = mStatements.GetCachedStatement(
michael@0 1226 NS_LITERAL_CSTRING("SELECT id, loads, last_load FROM moz_hosts WHERE "
michael@0 1227 "origin = :key;"));
michael@0 1228 }
michael@0 1229 NS_ENSURE_TRUE(stmt, false);
michael@0 1230 mozStorageStatementScoper scope(stmt);
michael@0 1231
michael@0 1232 nsresult rv = stmt->BindUTF8StringByName(NS_LITERAL_CSTRING("key"), key);
michael@0 1233 NS_ENSURE_SUCCESS(rv, false);
michael@0 1234
michael@0 1235 bool hasRows;
michael@0 1236 rv = stmt->ExecuteStep(&hasRows);
michael@0 1237 NS_ENSURE_SUCCESS(rv, false);
michael@0 1238
michael@0 1239 if (!hasRows) {
michael@0 1240 return false;
michael@0 1241 }
michael@0 1242
michael@0 1243 rv = stmt->GetInt32(0, &info.id);
michael@0 1244 NS_ENSURE_SUCCESS(rv, false);
michael@0 1245
michael@0 1246 rv = stmt->GetInt32(1, &info.loadCount);
michael@0 1247 NS_ENSURE_SUCCESS(rv, false);
michael@0 1248
michael@0 1249 rv = stmt->GetInt64(2, &info.lastLoad);
michael@0 1250 NS_ENSURE_SUCCESS(rv, false);
michael@0 1251
michael@0 1252 return true;
michael@0 1253 }
michael@0 1254
michael@0 1255 // Insert data about either a top-level page or a top-level origin into
michael@0 1256 // the database.
michael@0 1257 void
michael@0 1258 Seer::AddTopLevel(QueryType queryType, const nsACString &key, PRTime now)
michael@0 1259 {
michael@0 1260 MOZ_ASSERT(!NS_IsMainThread(), "AddTopLevel called on main thread.");
michael@0 1261
michael@0 1262 nsCOMPtr<mozIStorageStatement> stmt;
michael@0 1263 if (queryType == QUERY_PAGE) {
michael@0 1264 stmt = mStatements.GetCachedStatement(
michael@0 1265 NS_LITERAL_CSTRING("INSERT INTO moz_pages (uri, loads, last_load) "
michael@0 1266 "VALUES (:key, 1, :now);"));
michael@0 1267 } else {
michael@0 1268 stmt = mStatements.GetCachedStatement(
michael@0 1269 NS_LITERAL_CSTRING("INSERT INTO moz_hosts (origin, loads, last_load) "
michael@0 1270 "VALUES (:key, 1, :now);"));
michael@0 1271 }
michael@0 1272 if (!stmt) {
michael@0 1273 return;
michael@0 1274 }
michael@0 1275 mozStorageStatementScoper scope(stmt);
michael@0 1276
michael@0 1277 // Loading a page implicitly makes the seer learn about the page,
michael@0 1278 // so since we don't have it already, let's add it.
michael@0 1279 nsresult rv = stmt->BindUTF8StringByName(NS_LITERAL_CSTRING("key"), key);
michael@0 1280 RETURN_IF_FAILED(rv);
michael@0 1281
michael@0 1282 rv = stmt->BindInt64ByName(NS_LITERAL_CSTRING("now"), now);
michael@0 1283 RETURN_IF_FAILED(rv);
michael@0 1284
michael@0 1285 rv = stmt->Execute();
michael@0 1286 }
michael@0 1287
michael@0 1288 // Update data about either a top-level page or a top-level origin in the
michael@0 1289 // database.
michael@0 1290 void
michael@0 1291 Seer::UpdateTopLevel(QueryType queryType, const TopLevelInfo &info, PRTime now)
michael@0 1292 {
michael@0 1293 MOZ_ASSERT(!NS_IsMainThread(), "UpdateTopLevel called on main thread.");
michael@0 1294
michael@0 1295 nsCOMPtr<mozIStorageStatement> stmt;
michael@0 1296 if (queryType == QUERY_PAGE) {
michael@0 1297 stmt = mStatements.GetCachedStatement(
michael@0 1298 NS_LITERAL_CSTRING("UPDATE moz_pages SET loads = :load_count, "
michael@0 1299 "last_load = :now WHERE id = :id;"));
michael@0 1300 } else {
michael@0 1301 stmt = mStatements.GetCachedStatement(
michael@0 1302 NS_LITERAL_CSTRING("UPDATE moz_hosts SET loads = :load_count, "
michael@0 1303 "last_load = :now WHERE id = :id;"));
michael@0 1304 }
michael@0 1305 if (!stmt) {
michael@0 1306 return;
michael@0 1307 }
michael@0 1308 mozStorageStatementScoper scope(stmt);
michael@0 1309
michael@0 1310 int32_t newLoadCount = info.loadCount + 1;
michael@0 1311 if (newLoadCount <= 0) {
michael@0 1312 SEER_LOG(("Seer::UpdateTopLevel type %d id %d load count overflow\n",
michael@0 1313 queryType, info.id));
michael@0 1314 newLoadCount = info.loadCount;
michael@0 1315 ++mAccumulators->mLoadCountOverflows;
michael@0 1316 }
michael@0 1317
michael@0 1318 // First, let's update the page in the database, since loading a page
michael@0 1319 // implicitly learns about the page.
michael@0 1320 nsresult rv = stmt->BindInt32ByName(NS_LITERAL_CSTRING("load_count"),
michael@0 1321 newLoadCount);
michael@0 1322 RETURN_IF_FAILED(rv);
michael@0 1323
michael@0 1324 rv = stmt->BindInt64ByName(NS_LITERAL_CSTRING("now"), now);
michael@0 1325 RETURN_IF_FAILED(rv);
michael@0 1326
michael@0 1327 rv = stmt->BindInt32ByName(NS_LITERAL_CSTRING("id"), info.id);
michael@0 1328 RETURN_IF_FAILED(rv);
michael@0 1329
michael@0 1330 rv = stmt->Execute();
michael@0 1331 }
michael@0 1332
michael@0 1333 // Tries to predict for a top-level load (either page-based or origin-based).
michael@0 1334 // Returns false if it failed to predict at all, true if it did some sort of
michael@0 1335 // prediction.
michael@0 1336 // @param queryType - whether to predict based on page or origin
michael@0 1337 // @param info - the db info about the top-level resource
michael@0 1338 bool
michael@0 1339 Seer::TryPredict(QueryType queryType, const TopLevelInfo &info, PRTime now,
michael@0 1340 SeerVerifierHandle &verifier, TimeStamp &predictStartTime)
michael@0 1341 {
michael@0 1342 MOZ_ASSERT(!NS_IsMainThread(), "TryPredict called on main thread.");
michael@0 1343
michael@0 1344 if (!info.loadCount) {
michael@0 1345 SEER_LOG(("Seer::TryPredict info.loadCount is zero!\n"));
michael@0 1346 ++mAccumulators->mLoadCountZeroes;
michael@0 1347 return false;
michael@0 1348 }
michael@0 1349
michael@0 1350 int globalDegradation = CalculateGlobalDegradation(now, info.lastLoad);
michael@0 1351
michael@0 1352 // Now let's look up the subresources we know about for this page
michael@0 1353 nsCOMPtr<mozIStorageStatement> stmt;
michael@0 1354 if (queryType == QUERY_PAGE) {
michael@0 1355 stmt = mStatements.GetCachedStatement(
michael@0 1356 NS_LITERAL_CSTRING("SELECT uri, hits, last_hit FROM moz_subresources "
michael@0 1357 "WHERE pid = :id;"));
michael@0 1358 } else {
michael@0 1359 stmt = mStatements.GetCachedStatement(
michael@0 1360 NS_LITERAL_CSTRING("SELECT origin, hits, last_hit FROM moz_subhosts "
michael@0 1361 "WHERE hid = :id;"));
michael@0 1362 }
michael@0 1363 NS_ENSURE_TRUE(stmt, false);
michael@0 1364 mozStorageStatementScoper scope(stmt);
michael@0 1365
michael@0 1366 nsresult rv = stmt->BindInt32ByName(NS_LITERAL_CSTRING("id"), info.id);
michael@0 1367 NS_ENSURE_SUCCESS(rv, false);
michael@0 1368
michael@0 1369 bool hasRows;
michael@0 1370 rv = stmt->ExecuteStep(&hasRows);
michael@0 1371 if (NS_FAILED(rv) || !hasRows) {
michael@0 1372 return false;
michael@0 1373 }
michael@0 1374
michael@0 1375 nsRefPtr<SeerPredictionRunner> runner =
michael@0 1376 new SeerPredictionRunner(verifier, predictStartTime);
michael@0 1377
michael@0 1378 while (hasRows) {
michael@0 1379 int32_t hitCount;
michael@0 1380 PRTime lastHit;
michael@0 1381 nsAutoCString subresource;
michael@0 1382 int baseConfidence, confidence;
michael@0 1383
michael@0 1384 // We use goto nextrow here instead of just failling, because we want
michael@0 1385 // to do some sort of prediction if at all possible. Of course, it's
michael@0 1386 // probably unlikely that subsequent rows will succeed if one fails, but
michael@0 1387 // it's worth a shot.
michael@0 1388
michael@0 1389 rv = stmt->GetUTF8String(0, subresource);
michael@0 1390 if NS_FAILED(rv) {
michael@0 1391 goto nextrow;
michael@0 1392 }
michael@0 1393
michael@0 1394 rv = stmt->GetInt32(1, &hitCount);
michael@0 1395 if (NS_FAILED(rv)) {
michael@0 1396 goto nextrow;
michael@0 1397 }
michael@0 1398
michael@0 1399 rv = stmt->GetInt64(2, &lastHit);
michael@0 1400 if (NS_FAILED(rv)) {
michael@0 1401 goto nextrow;
michael@0 1402 }
michael@0 1403
michael@0 1404 baseConfidence = (hitCount * 100) / info.loadCount;
michael@0 1405 confidence = CalculateConfidence(baseConfidence, lastHit, info.lastLoad,
michael@0 1406 globalDegradation);
michael@0 1407 SetupPrediction(confidence, subresource, runner);
michael@0 1408
michael@0 1409 nextrow:
michael@0 1410 rv = stmt->ExecuteStep(&hasRows);
michael@0 1411 NS_ENSURE_SUCCESS(rv, false);
michael@0 1412 }
michael@0 1413
michael@0 1414 bool predicted = false;
michael@0 1415
michael@0 1416 if (runner->HasWork()) {
michael@0 1417 NS_DispatchToMainThread(runner);
michael@0 1418 predicted = true;
michael@0 1419 }
michael@0 1420
michael@0 1421 return predicted;
michael@0 1422 }
michael@0 1423
michael@0 1424 // Find out if a top-level page is likely to redirect.
michael@0 1425 bool
michael@0 1426 Seer::WouldRedirect(const TopLevelInfo &info, PRTime now, UriInfo &newUri)
michael@0 1427 {
michael@0 1428 MOZ_ASSERT(!NS_IsMainThread(), "WouldRedirect called on main thread.");
michael@0 1429
michael@0 1430 if (!info.loadCount) {
michael@0 1431 SEER_LOG(("Seer::WouldRedirect info.loadCount is zero!\n"));
michael@0 1432 ++mAccumulators->mLoadCountZeroes;
michael@0 1433 return false;
michael@0 1434 }
michael@0 1435
michael@0 1436 nsCOMPtr<mozIStorageStatement> stmt = mStatements.GetCachedStatement(
michael@0 1437 NS_LITERAL_CSTRING("SELECT uri, origin, hits, last_hit "
michael@0 1438 "FROM moz_redirects WHERE pid = :id;"));
michael@0 1439 NS_ENSURE_TRUE(stmt, false);
michael@0 1440 mozStorageStatementScoper scope(stmt);
michael@0 1441
michael@0 1442 nsresult rv = stmt->BindInt32ByName(NS_LITERAL_CSTRING("id"), info.id);
michael@0 1443 NS_ENSURE_SUCCESS(rv, false);
michael@0 1444
michael@0 1445 bool hasRows;
michael@0 1446 rv = stmt->ExecuteStep(&hasRows);
michael@0 1447 if (NS_FAILED(rv) || !hasRows) {
michael@0 1448 return false;
michael@0 1449 }
michael@0 1450
michael@0 1451 rv = stmt->GetUTF8String(0, newUri.spec);
michael@0 1452 NS_ENSURE_SUCCESS(rv, false);
michael@0 1453
michael@0 1454 rv = stmt->GetUTF8String(1, newUri.origin);
michael@0 1455 NS_ENSURE_SUCCESS(rv, false);
michael@0 1456
michael@0 1457 int32_t hitCount;
michael@0 1458 rv = stmt->GetInt32(2, &hitCount);
michael@0 1459 NS_ENSURE_SUCCESS(rv, false);
michael@0 1460
michael@0 1461 PRTime lastHit;
michael@0 1462 rv = stmt->GetInt64(3, &lastHit);
michael@0 1463 NS_ENSURE_SUCCESS(rv, false);
michael@0 1464
michael@0 1465 int globalDegradation = CalculateGlobalDegradation(now, info.lastLoad);
michael@0 1466 int baseConfidence = (hitCount * 100) / info.loadCount;
michael@0 1467 int confidence = CalculateConfidence(baseConfidence, lastHit, info.lastLoad,
michael@0 1468 globalDegradation);
michael@0 1469
michael@0 1470 if (confidence > mRedirectLikelyConfidence) {
michael@0 1471 return true;
michael@0 1472 }
michael@0 1473
michael@0 1474 return false;
michael@0 1475 }
michael@0 1476
michael@0 1477 // This will add a page to our list of startup pages if it's being loaded
michael@0 1478 // before our startup window has expired.
michael@0 1479 void
michael@0 1480 Seer::MaybeLearnForStartup(const UriInfo &uri, const PRTime now)
michael@0 1481 {
michael@0 1482 MOZ_ASSERT(!NS_IsMainThread(), "MaybeLearnForStartup called on main thread.");
michael@0 1483
michael@0 1484 if ((now - mStartupTime) < STARTUP_WINDOW) {
michael@0 1485 LearnForStartup(uri);
michael@0 1486 }
michael@0 1487 }
michael@0 1488
michael@0 1489 const int MAX_PAGELOAD_DEPTH = 10;
michael@0 1490
michael@0 1491 // This is the driver for prediction based on a new pageload.
michael@0 1492 void
michael@0 1493 Seer::PredictForPageload(const UriInfo &uri, SeerVerifierHandle &verifier,
michael@0 1494 int stackCount, TimeStamp &predictStartTime)
michael@0 1495 {
michael@0 1496 MOZ_ASSERT(!NS_IsMainThread(), "PredictForPageload called on main thread.");
michael@0 1497
michael@0 1498 if (stackCount > MAX_PAGELOAD_DEPTH) {
michael@0 1499 SEER_LOG(("Too deep into pageload prediction"));
michael@0 1500 return;
michael@0 1501 }
michael@0 1502
michael@0 1503 if (NS_FAILED(EnsureInitStorage())) {
michael@0 1504 return;
michael@0 1505 }
michael@0 1506
michael@0 1507 PRTime now = PR_Now();
michael@0 1508
michael@0 1509 MaybeLearnForStartup(uri, now);
michael@0 1510
michael@0 1511 TopLevelInfo pageInfo;
michael@0 1512 TopLevelInfo originInfo;
michael@0 1513 bool havePage = LookupTopLevel(QUERY_PAGE, uri.spec, pageInfo);
michael@0 1514 bool haveOrigin = LookupTopLevel(QUERY_ORIGIN, uri.origin, originInfo);
michael@0 1515
michael@0 1516 if (!havePage) {
michael@0 1517 AddTopLevel(QUERY_PAGE, uri.spec, now);
michael@0 1518 } else {
michael@0 1519 UpdateTopLevel(QUERY_PAGE, pageInfo, now);
michael@0 1520 }
michael@0 1521
michael@0 1522 if (!haveOrigin) {
michael@0 1523 AddTopLevel(QUERY_ORIGIN, uri.origin, now);
michael@0 1524 } else {
michael@0 1525 UpdateTopLevel(QUERY_ORIGIN, originInfo, now);
michael@0 1526 }
michael@0 1527
michael@0 1528 UriInfo newUri;
michael@0 1529 if (havePage && WouldRedirect(pageInfo, now, newUri)) {
michael@0 1530 nsRefPtr<SeerPredictionRunner> runner =
michael@0 1531 new SeerPredictionRunner(verifier, predictStartTime);
michael@0 1532 runner->AddPreconnect(newUri.spec);
michael@0 1533 NS_DispatchToMainThread(runner);
michael@0 1534 PredictForPageload(newUri, verifier, stackCount + 1, predictStartTime);
michael@0 1535 return;
michael@0 1536 }
michael@0 1537
michael@0 1538 bool predicted = false;
michael@0 1539
michael@0 1540 // We always try to be as specific as possible in our predictions, so try
michael@0 1541 // to predict based on the full URI before we fall back to the origin.
michael@0 1542 if (havePage) {
michael@0 1543 predicted = TryPredict(QUERY_PAGE, pageInfo, now, verifier,
michael@0 1544 predictStartTime);
michael@0 1545 }
michael@0 1546
michael@0 1547 if (!predicted && haveOrigin) {
michael@0 1548 predicted = TryPredict(QUERY_ORIGIN, originInfo, now, verifier,
michael@0 1549 predictStartTime);
michael@0 1550 }
michael@0 1551
michael@0 1552 if (!predicted) {
michael@0 1553 Telemetry::AccumulateTimeDelta(Telemetry::SEER_PREDICT_TIME_TO_INACTION,
michael@0 1554 predictStartTime);
michael@0 1555 }
michael@0 1556 }
michael@0 1557
michael@0 1558 // This is the driver for predicting at browser startup time based on pages that
michael@0 1559 // have previously been loaded close to startup.
michael@0 1560 void
michael@0 1561 Seer::PredictForStartup(SeerVerifierHandle &verifier,
michael@0 1562 TimeStamp &predictStartTime)
michael@0 1563 {
michael@0 1564 MOZ_ASSERT(!NS_IsMainThread(), "PredictForStartup called on main thread");
michael@0 1565
michael@0 1566 if (!mStartupCount) {
michael@0 1567 SEER_LOG(("Seer::PredictForStartup mStartupCount is zero!\n"));
michael@0 1568 ++mAccumulators->mStartupCountZeroes;
michael@0 1569 return;
michael@0 1570 }
michael@0 1571
michael@0 1572 if (NS_FAILED(EnsureInitStorage())) {
michael@0 1573 return;
michael@0 1574 }
michael@0 1575
michael@0 1576 nsCOMPtr<mozIStorageStatement> stmt = mStatements.GetCachedStatement(
michael@0 1577 NS_LITERAL_CSTRING("SELECT uri, hits, last_hit FROM moz_startup_pages;"));
michael@0 1578 if (!stmt) {
michael@0 1579 return;
michael@0 1580 }
michael@0 1581 mozStorageStatementScoper scope(stmt);
michael@0 1582 nsresult rv;
michael@0 1583 bool hasRows;
michael@0 1584
michael@0 1585 nsRefPtr<SeerPredictionRunner> runner =
michael@0 1586 new SeerPredictionRunner(verifier, predictStartTime);
michael@0 1587
michael@0 1588 rv = stmt->ExecuteStep(&hasRows);
michael@0 1589 RETURN_IF_FAILED(rv);
michael@0 1590
michael@0 1591 while (hasRows) {
michael@0 1592 nsAutoCString uri;
michael@0 1593 int32_t hitCount;
michael@0 1594 PRTime lastHit;
michael@0 1595 int baseConfidence, confidence;
michael@0 1596
michael@0 1597 // We use goto nextrow here instead of just failling, because we want
michael@0 1598 // to do some sort of prediction if at all possible. Of course, it's
michael@0 1599 // probably unlikely that subsequent rows will succeed if one fails, but
michael@0 1600 // it's worth a shot.
michael@0 1601
michael@0 1602 rv = stmt->GetUTF8String(0, uri);
michael@0 1603 if (NS_FAILED(rv)) {
michael@0 1604 goto nextrow;
michael@0 1605 }
michael@0 1606
michael@0 1607 rv = stmt->GetInt32(1, &hitCount);
michael@0 1608 if (NS_FAILED(rv)) {
michael@0 1609 goto nextrow;
michael@0 1610 }
michael@0 1611
michael@0 1612 rv = stmt->GetInt64(2, &lastHit);
michael@0 1613 if (NS_FAILED(rv)) {
michael@0 1614 goto nextrow;
michael@0 1615 }
michael@0 1616
michael@0 1617 baseConfidence = (hitCount * 100) / mStartupCount;
michael@0 1618 confidence = CalculateConfidence(baseConfidence, lastHit,
michael@0 1619 mLastStartupTime, 0);
michael@0 1620 SetupPrediction(confidence, uri, runner);
michael@0 1621
michael@0 1622 nextrow:
michael@0 1623 rv = stmt->ExecuteStep(&hasRows);
michael@0 1624 RETURN_IF_FAILED(rv);
michael@0 1625 }
michael@0 1626
michael@0 1627 if (runner->HasWork()) {
michael@0 1628 NS_DispatchToMainThread(runner);
michael@0 1629 } else {
michael@0 1630 Telemetry::AccumulateTimeDelta(Telemetry::SEER_PREDICT_TIME_TO_INACTION,
michael@0 1631 predictStartTime);
michael@0 1632 }
michael@0 1633 }
michael@0 1634
michael@0 1635 // All URIs we get passed *must* be http or https if they're not null. This
michael@0 1636 // helps ensure that.
michael@0 1637 static bool
michael@0 1638 IsNullOrHttp(nsIURI *uri)
michael@0 1639 {
michael@0 1640 if (!uri) {
michael@0 1641 return true;
michael@0 1642 }
michael@0 1643
michael@0 1644 bool isHTTP = false;
michael@0 1645 uri->SchemeIs("http", &isHTTP);
michael@0 1646 if (!isHTTP) {
michael@0 1647 uri->SchemeIs("https", &isHTTP);
michael@0 1648 }
michael@0 1649
michael@0 1650 return isHTTP;
michael@0 1651 }
michael@0 1652
michael@0 1653 nsresult
michael@0 1654 Seer::ReserveSpaceInQueue()
michael@0 1655 {
michael@0 1656 MutexAutoLock lock(mQueueSizeLock);
michael@0 1657
michael@0 1658 if (mQueueSize >= mMaxQueueSize) {
michael@0 1659 SEER_LOG(("Not enqueuing event - queue too large"));
michael@0 1660 return NS_ERROR_NOT_AVAILABLE;
michael@0 1661 }
michael@0 1662
michael@0 1663 mQueueSize++;
michael@0 1664 return NS_OK;
michael@0 1665 }
michael@0 1666
michael@0 1667 void
michael@0 1668 Seer::FreeSpaceInQueue()
michael@0 1669 {
michael@0 1670 MutexAutoLock lock(mQueueSizeLock);
michael@0 1671 MOZ_ASSERT(mQueueSize > 0, "unexpected mQueueSize");
michael@0 1672 mQueueSize--;
michael@0 1673 }
michael@0 1674
michael@0 1675 // Called from the main thread to initiate predictive actions
michael@0 1676 NS_IMETHODIMP
michael@0 1677 Seer::Predict(nsIURI *targetURI, nsIURI *sourceURI, SeerPredictReason reason,
michael@0 1678 nsILoadContext *loadContext, nsINetworkSeerVerifier *verifier)
michael@0 1679 {
michael@0 1680 MOZ_ASSERT(NS_IsMainThread(),
michael@0 1681 "Seer interface methods must be called on the main thread");
michael@0 1682
michael@0 1683 if (!mInitialized) {
michael@0 1684 return NS_ERROR_NOT_AVAILABLE;
michael@0 1685 }
michael@0 1686
michael@0 1687 if (!mEnabled) {
michael@0 1688 return NS_ERROR_NOT_AVAILABLE;
michael@0 1689 }
michael@0 1690
michael@0 1691 if (loadContext && loadContext->UsePrivateBrowsing()) {
michael@0 1692 // Don't want to do anything in PB mode
michael@0 1693 return NS_OK;
michael@0 1694 }
michael@0 1695
michael@0 1696 if (!IsNullOrHttp(targetURI) || !IsNullOrHttp(sourceURI)) {
michael@0 1697 // Nothing we can do for non-HTTP[S] schemes
michael@0 1698 return NS_OK;
michael@0 1699 }
michael@0 1700
michael@0 1701 // Ensure we've been given the appropriate arguments for the kind of
michael@0 1702 // prediction we're being asked to do
michael@0 1703 switch (reason) {
michael@0 1704 case nsINetworkSeer::PREDICT_LINK:
michael@0 1705 if (!targetURI || !sourceURI) {
michael@0 1706 return NS_ERROR_INVALID_ARG;
michael@0 1707 }
michael@0 1708 // Link hover is a special case where we can predict without hitting the
michael@0 1709 // db, so let's go ahead and fire off that prediction here.
michael@0 1710 PredictForLink(targetURI, sourceURI, verifier);
michael@0 1711 return NS_OK;
michael@0 1712 case nsINetworkSeer::PREDICT_LOAD:
michael@0 1713 if (!targetURI || sourceURI) {
michael@0 1714 return NS_ERROR_INVALID_ARG;
michael@0 1715 }
michael@0 1716 break;
michael@0 1717 case nsINetworkSeer::PREDICT_STARTUP:
michael@0 1718 if (targetURI || sourceURI) {
michael@0 1719 return NS_ERROR_INVALID_ARG;
michael@0 1720 }
michael@0 1721 break;
michael@0 1722 default:
michael@0 1723 return NS_ERROR_INVALID_ARG;
michael@0 1724 }
michael@0 1725
michael@0 1726 ++mAccumulators->mPredictAttempts;
michael@0 1727 nsresult rv = ReserveSpaceInQueue();
michael@0 1728 if (NS_FAILED(rv)) {
michael@0 1729 ++mAccumulators->mPredictFullQueue;
michael@0 1730 return NS_ERROR_NOT_AVAILABLE;
michael@0 1731 }
michael@0 1732
michael@0 1733 nsRefPtr<SeerPredictionEvent> event = new SeerPredictionEvent(targetURI,
michael@0 1734 sourceURI,
michael@0 1735 reason,
michael@0 1736 verifier);
michael@0 1737 return mIOThread->Dispatch(event, NS_DISPATCH_NORMAL);
michael@0 1738 }
michael@0 1739
michael@0 1740 // A runnable for updating our information in the database. This must always
michael@0 1741 // be dispatched to the seer thread.
michael@0 1742 class SeerLearnEvent : public nsRunnable
michael@0 1743 {
michael@0 1744 public:
michael@0 1745 SeerLearnEvent(nsIURI *targetURI, nsIURI *sourceURI, SeerLearnReason reason)
michael@0 1746 :mReason(reason)
michael@0 1747 {
michael@0 1748 MOZ_ASSERT(NS_IsMainThread(), "Creating learn event off main thread");
michael@0 1749
michael@0 1750 mEnqueueTime = TimeStamp::Now();
michael@0 1751
michael@0 1752 targetURI->GetAsciiSpec(mTargetURI.spec);
michael@0 1753 ExtractOrigin(targetURI, mTargetURI.origin);
michael@0 1754 if (sourceURI) {
michael@0 1755 sourceURI->GetAsciiSpec(mSourceURI.spec);
michael@0 1756 ExtractOrigin(sourceURI, mSourceURI.origin);
michael@0 1757 }
michael@0 1758 }
michael@0 1759
michael@0 1760 NS_IMETHOD Run() MOZ_OVERRIDE
michael@0 1761 {
michael@0 1762 MOZ_ASSERT(!NS_IsMainThread(), "Running learn off main thread");
michael@0 1763
michael@0 1764 nsresult rv = NS_OK;
michael@0 1765
michael@0 1766 Telemetry::AccumulateTimeDelta(Telemetry::SEER_LEARN_WAIT_TIME,
michael@0 1767 mEnqueueTime);
michael@0 1768
michael@0 1769 TimeStamp startTime = TimeStamp::Now();
michael@0 1770
michael@0 1771 switch (mReason) {
michael@0 1772 case nsINetworkSeer::LEARN_LOAD_TOPLEVEL:
michael@0 1773 gSeer->LearnForToplevel(mTargetURI);
michael@0 1774 break;
michael@0 1775 case nsINetworkSeer::LEARN_LOAD_REDIRECT:
michael@0 1776 gSeer->LearnForRedirect(mTargetURI, mSourceURI);
michael@0 1777 break;
michael@0 1778 case nsINetworkSeer::LEARN_LOAD_SUBRESOURCE:
michael@0 1779 gSeer->LearnForSubresource(mTargetURI, mSourceURI);
michael@0 1780 break;
michael@0 1781 case nsINetworkSeer::LEARN_STARTUP:
michael@0 1782 gSeer->LearnForStartup(mTargetURI);
michael@0 1783 break;
michael@0 1784 default:
michael@0 1785 MOZ_ASSERT(false, "Got unexpected value for learn reason");
michael@0 1786 rv = NS_ERROR_UNEXPECTED;
michael@0 1787 }
michael@0 1788
michael@0 1789 gSeer->FreeSpaceInQueue();
michael@0 1790
michael@0 1791 Telemetry::AccumulateTimeDelta(Telemetry::SEER_LEARN_WORK_TIME, startTime);
michael@0 1792
michael@0 1793 gSeer->MaybeScheduleCleanup();
michael@0 1794
michael@0 1795 return rv;
michael@0 1796 }
michael@0 1797 private:
michael@0 1798 Seer::UriInfo mTargetURI;
michael@0 1799 Seer::UriInfo mSourceURI;
michael@0 1800 SeerLearnReason mReason;
michael@0 1801 TimeStamp mEnqueueTime;
michael@0 1802 };
michael@0 1803
michael@0 1804 void
michael@0 1805 Seer::LearnForToplevel(const UriInfo &uri)
michael@0 1806 {
michael@0 1807 MOZ_ASSERT(!NS_IsMainThread(), "LearnForToplevel called on main thread.");
michael@0 1808
michael@0 1809 if (NS_FAILED(EnsureInitStorage())) {
michael@0 1810 return;
michael@0 1811 }
michael@0 1812
michael@0 1813 PRTime now = PR_Now();
michael@0 1814
michael@0 1815 MaybeLearnForStartup(uri, now);
michael@0 1816
michael@0 1817 TopLevelInfo pageInfo;
michael@0 1818 TopLevelInfo originInfo;
michael@0 1819 bool havePage = LookupTopLevel(QUERY_PAGE, uri.spec, pageInfo);
michael@0 1820 bool haveOrigin = LookupTopLevel(QUERY_ORIGIN, uri.origin, originInfo);
michael@0 1821
michael@0 1822 if (!havePage) {
michael@0 1823 AddTopLevel(QUERY_PAGE, uri.spec, now);
michael@0 1824 } else {
michael@0 1825 UpdateTopLevel(QUERY_PAGE, pageInfo, now);
michael@0 1826 }
michael@0 1827
michael@0 1828 if (!haveOrigin) {
michael@0 1829 AddTopLevel(QUERY_ORIGIN, uri.origin, now);
michael@0 1830 } else {
michael@0 1831 UpdateTopLevel(QUERY_ORIGIN, originInfo, now);
michael@0 1832 }
michael@0 1833 }
michael@0 1834
michael@0 1835 // Queries to look up information about a *specific* subresource associated
michael@0 1836 // with a *specific* top-level load.
michael@0 1837 bool
michael@0 1838 Seer::LookupSubresource(QueryType queryType, const int32_t parentId,
michael@0 1839 const nsACString &key, SubresourceInfo &info)
michael@0 1840 {
michael@0 1841 MOZ_ASSERT(!NS_IsMainThread(), "LookupSubresource called on main thread.");
michael@0 1842
michael@0 1843 nsCOMPtr<mozIStorageStatement> stmt;
michael@0 1844 if (queryType == QUERY_PAGE) {
michael@0 1845 stmt = mStatements.GetCachedStatement(
michael@0 1846 NS_LITERAL_CSTRING("SELECT id, hits, last_hit FROM moz_subresources "
michael@0 1847 "WHERE pid = :parent_id AND uri = :key;"));
michael@0 1848 } else {
michael@0 1849 stmt = mStatements.GetCachedStatement(
michael@0 1850 NS_LITERAL_CSTRING("SELECT id, hits, last_hit FROM moz_subhosts WHERE "
michael@0 1851 "hid = :parent_id AND origin = :key;"));
michael@0 1852 }
michael@0 1853 NS_ENSURE_TRUE(stmt, false);
michael@0 1854 mozStorageStatementScoper scope(stmt);
michael@0 1855
michael@0 1856 nsresult rv = stmt->BindInt32ByName(NS_LITERAL_CSTRING("parent_id"),
michael@0 1857 parentId);
michael@0 1858 NS_ENSURE_SUCCESS(rv, false);
michael@0 1859
michael@0 1860 rv = stmt->BindUTF8StringByName(NS_LITERAL_CSTRING("key"), key);
michael@0 1861 NS_ENSURE_SUCCESS(rv, false);
michael@0 1862
michael@0 1863 bool hasRows;
michael@0 1864 rv = stmt->ExecuteStep(&hasRows);
michael@0 1865 NS_ENSURE_SUCCESS(rv, false);
michael@0 1866 if (!hasRows) {
michael@0 1867 return false;
michael@0 1868 }
michael@0 1869
michael@0 1870 rv = stmt->GetInt32(0, &info.id);
michael@0 1871 NS_ENSURE_SUCCESS(rv, false);
michael@0 1872
michael@0 1873 rv = stmt->GetInt32(1, &info.hitCount);
michael@0 1874 NS_ENSURE_SUCCESS(rv, false);
michael@0 1875
michael@0 1876 rv = stmt->GetInt64(2, &info.lastHit);
michael@0 1877 NS_ENSURE_SUCCESS(rv, false);
michael@0 1878
michael@0 1879 return true;
michael@0 1880 }
michael@0 1881
michael@0 1882 // Add information about a new subresource associated with a top-level load.
michael@0 1883 void
michael@0 1884 Seer::AddSubresource(QueryType queryType, const int32_t parentId,
michael@0 1885 const nsACString &key, const PRTime now)
michael@0 1886 {
michael@0 1887 MOZ_ASSERT(!NS_IsMainThread(), "AddSubresource called on main thread.");
michael@0 1888
michael@0 1889 nsCOMPtr<mozIStorageStatement> stmt;
michael@0 1890 if (queryType == QUERY_PAGE) {
michael@0 1891 stmt = mStatements.GetCachedStatement(
michael@0 1892 NS_LITERAL_CSTRING("INSERT INTO moz_subresources "
michael@0 1893 "(pid, uri, hits, last_hit) VALUES "
michael@0 1894 "(:parent_id, :key, 1, :now);"));
michael@0 1895 } else {
michael@0 1896 stmt = mStatements.GetCachedStatement(
michael@0 1897 NS_LITERAL_CSTRING("INSERT INTO moz_subhosts "
michael@0 1898 "(hid, origin, hits, last_hit) VALUES "
michael@0 1899 "(:parent_id, :key, 1, :now);"));
michael@0 1900 }
michael@0 1901 if (!stmt) {
michael@0 1902 return;
michael@0 1903 }
michael@0 1904 mozStorageStatementScoper scope(stmt);
michael@0 1905
michael@0 1906 nsresult rv = stmt->BindInt32ByName(NS_LITERAL_CSTRING("parent_id"),
michael@0 1907 parentId);
michael@0 1908 RETURN_IF_FAILED(rv);
michael@0 1909
michael@0 1910 rv = stmt->BindUTF8StringByName(NS_LITERAL_CSTRING("key"), key);
michael@0 1911 RETURN_IF_FAILED(rv);
michael@0 1912
michael@0 1913 rv = stmt->BindInt64ByName(NS_LITERAL_CSTRING("now"), now);
michael@0 1914 RETURN_IF_FAILED(rv);
michael@0 1915
michael@0 1916 rv = stmt->Execute();
michael@0 1917 }
michael@0 1918
michael@0 1919 // Update the information about a particular subresource associated with a
michael@0 1920 // top-level load
michael@0 1921 void
michael@0 1922 Seer::UpdateSubresource(QueryType queryType, const SubresourceInfo &info,
michael@0 1923 const PRTime now)
michael@0 1924 {
michael@0 1925 MOZ_ASSERT(!NS_IsMainThread(), "UpdateSubresource called on main thread.");
michael@0 1926
michael@0 1927 nsCOMPtr<mozIStorageStatement> stmt;
michael@0 1928 if (queryType == QUERY_PAGE) {
michael@0 1929 stmt = mStatements.GetCachedStatement(
michael@0 1930 NS_LITERAL_CSTRING("UPDATE moz_subresources SET hits = :hit_count, "
michael@0 1931 "last_hit = :now WHERE id = :id;"));
michael@0 1932 } else {
michael@0 1933 stmt = mStatements.GetCachedStatement(
michael@0 1934 NS_LITERAL_CSTRING("UPDATE moz_subhosts SET hits = :hit_count, "
michael@0 1935 "last_hit = :now WHERE id = :id;"));
michael@0 1936 }
michael@0 1937 if (!stmt) {
michael@0 1938 return;
michael@0 1939 }
michael@0 1940 mozStorageStatementScoper scope(stmt);
michael@0 1941
michael@0 1942 nsresult rv = stmt->BindInt32ByName(NS_LITERAL_CSTRING("hit_count"),
michael@0 1943 info.hitCount + 1);
michael@0 1944 RETURN_IF_FAILED(rv);
michael@0 1945
michael@0 1946 rv = stmt->BindInt64ByName(NS_LITERAL_CSTRING("now"), now);
michael@0 1947 RETURN_IF_FAILED(rv);
michael@0 1948
michael@0 1949 rv = stmt->BindInt32ByName(NS_LITERAL_CSTRING("id"), info.id);
michael@0 1950 RETURN_IF_FAILED(rv);
michael@0 1951
michael@0 1952 rv = stmt->Execute();
michael@0 1953 }
michael@0 1954
michael@0 1955 // Called when a subresource has been hit from a top-level load. Uses the two
michael@0 1956 // helper functions above to update the database appropriately.
michael@0 1957 void
michael@0 1958 Seer::LearnForSubresource(const UriInfo &targetURI, const UriInfo &sourceURI)
michael@0 1959 {
michael@0 1960 MOZ_ASSERT(!NS_IsMainThread(), "LearnForSubresource called on main thread.");
michael@0 1961
michael@0 1962 if (NS_FAILED(EnsureInitStorage())) {
michael@0 1963 return;
michael@0 1964 }
michael@0 1965
michael@0 1966 TopLevelInfo pageInfo, originInfo;
michael@0 1967 bool havePage = LookupTopLevel(QUERY_PAGE, sourceURI.spec, pageInfo);
michael@0 1968 bool haveOrigin = LookupTopLevel(QUERY_ORIGIN, sourceURI.origin,
michael@0 1969 originInfo);
michael@0 1970
michael@0 1971 if (!havePage && !haveOrigin) {
michael@0 1972 // Nothing to do, since we know nothing about the top level resource
michael@0 1973 return;
michael@0 1974 }
michael@0 1975
michael@0 1976 SubresourceInfo resourceInfo;
michael@0 1977 bool haveResource = false;
michael@0 1978 if (havePage) {
michael@0 1979 haveResource = LookupSubresource(QUERY_PAGE, pageInfo.id, targetURI.spec,
michael@0 1980 resourceInfo);
michael@0 1981 }
michael@0 1982
michael@0 1983 SubresourceInfo hostInfo;
michael@0 1984 bool haveHost = false;
michael@0 1985 if (haveOrigin) {
michael@0 1986 haveHost = LookupSubresource(QUERY_ORIGIN, originInfo.id, targetURI.origin,
michael@0 1987 hostInfo);
michael@0 1988 }
michael@0 1989
michael@0 1990 PRTime now = PR_Now();
michael@0 1991
michael@0 1992 if (haveResource) {
michael@0 1993 UpdateSubresource(QUERY_PAGE, resourceInfo, now);
michael@0 1994 } else if (havePage) {
michael@0 1995 AddSubresource(QUERY_PAGE, pageInfo.id, targetURI.spec, now);
michael@0 1996 }
michael@0 1997 // Can't add a subresource to a page we don't have in our db.
michael@0 1998
michael@0 1999 if (haveHost) {
michael@0 2000 UpdateSubresource(QUERY_ORIGIN, hostInfo, now);
michael@0 2001 } else if (haveOrigin) {
michael@0 2002 AddSubresource(QUERY_ORIGIN, originInfo.id, targetURI.origin, now);
michael@0 2003 }
michael@0 2004 // Can't add a subhost to a host we don't have in our db
michael@0 2005 }
michael@0 2006
michael@0 2007 // This is called when a top-level loaded ended up redirecting to a different
michael@0 2008 // URI so we can keep track of that fact.
michael@0 2009 void
michael@0 2010 Seer::LearnForRedirect(const UriInfo &targetURI, const UriInfo &sourceURI)
michael@0 2011 {
michael@0 2012 MOZ_ASSERT(!NS_IsMainThread(), "LearnForRedirect called on main thread.");
michael@0 2013
michael@0 2014 if (NS_FAILED(EnsureInitStorage())) {
michael@0 2015 return;
michael@0 2016 }
michael@0 2017
michael@0 2018 PRTime now = PR_Now();
michael@0 2019 nsresult rv;
michael@0 2020
michael@0 2021 nsCOMPtr<mozIStorageStatement> getPage = mStatements.GetCachedStatement(
michael@0 2022 NS_LITERAL_CSTRING("SELECT id FROM moz_pages WHERE uri = :spec;"));
michael@0 2023 if (!getPage) {
michael@0 2024 return;
michael@0 2025 }
michael@0 2026 mozStorageStatementScoper scopedPage(getPage);
michael@0 2027
michael@0 2028 // look up source in moz_pages
michael@0 2029 rv = getPage->BindUTF8StringByName(NS_LITERAL_CSTRING("spec"),
michael@0 2030 sourceURI.spec);
michael@0 2031 RETURN_IF_FAILED(rv);
michael@0 2032
michael@0 2033 bool hasRows;
michael@0 2034 rv = getPage->ExecuteStep(&hasRows);
michael@0 2035 if (NS_FAILED(rv) || !hasRows) {
michael@0 2036 return;
michael@0 2037 }
michael@0 2038
michael@0 2039 int32_t pageId;
michael@0 2040 rv = getPage->GetInt32(0, &pageId);
michael@0 2041 RETURN_IF_FAILED(rv);
michael@0 2042
michael@0 2043 nsCOMPtr<mozIStorageStatement> getRedirect = mStatements.GetCachedStatement(
michael@0 2044 NS_LITERAL_CSTRING("SELECT id, hits FROM moz_redirects WHERE "
michael@0 2045 "pid = :page_id AND uri = :spec;"));
michael@0 2046 if (!getRedirect) {
michael@0 2047 return;
michael@0 2048 }
michael@0 2049 mozStorageStatementScoper scopedRedirect(getRedirect);
michael@0 2050
michael@0 2051 rv = getRedirect->BindInt32ByName(NS_LITERAL_CSTRING("page_id"), pageId);
michael@0 2052 RETURN_IF_FAILED(rv);
michael@0 2053
michael@0 2054 rv = getRedirect->BindUTF8StringByName(NS_LITERAL_CSTRING("spec"),
michael@0 2055 targetURI.spec);
michael@0 2056 RETURN_IF_FAILED(rv);
michael@0 2057
michael@0 2058 rv = getRedirect->ExecuteStep(&hasRows);
michael@0 2059 RETURN_IF_FAILED(rv);
michael@0 2060
michael@0 2061 if (!hasRows) {
michael@0 2062 // This is the first time we've seen this top-level redirect to this URI
michael@0 2063 nsCOMPtr<mozIStorageStatement> addRedirect = mStatements.GetCachedStatement(
michael@0 2064 NS_LITERAL_CSTRING("INSERT INTO moz_redirects "
michael@0 2065 "(pid, uri, origin, hits, last_hit) VALUES "
michael@0 2066 "(:page_id, :spec, :origin, 1, :now);"));
michael@0 2067 if (!addRedirect) {
michael@0 2068 return;
michael@0 2069 }
michael@0 2070 mozStorageStatementScoper scopedAdd(addRedirect);
michael@0 2071
michael@0 2072 rv = addRedirect->BindInt32ByName(NS_LITERAL_CSTRING("page_id"), pageId);
michael@0 2073 RETURN_IF_FAILED(rv);
michael@0 2074
michael@0 2075 rv = addRedirect->BindUTF8StringByName(NS_LITERAL_CSTRING("spec"),
michael@0 2076 targetURI.spec);
michael@0 2077 RETURN_IF_FAILED(rv);
michael@0 2078
michael@0 2079 rv = addRedirect->BindUTF8StringByName(NS_LITERAL_CSTRING("origin"),
michael@0 2080 targetURI.origin);
michael@0 2081 RETURN_IF_FAILED(rv);
michael@0 2082
michael@0 2083 rv = addRedirect->BindInt64ByName(NS_LITERAL_CSTRING("now"), now);
michael@0 2084 RETURN_IF_FAILED(rv);
michael@0 2085
michael@0 2086 rv = addRedirect->Execute();
michael@0 2087 } else {
michael@0 2088 // We've seen this redirect before
michael@0 2089 int32_t redirId, hits;
michael@0 2090 rv = getRedirect->GetInt32(0, &redirId);
michael@0 2091 RETURN_IF_FAILED(rv);
michael@0 2092
michael@0 2093 rv = getRedirect->GetInt32(1, &hits);
michael@0 2094 RETURN_IF_FAILED(rv);
michael@0 2095
michael@0 2096 nsCOMPtr<mozIStorageStatement> updateRedirect =
michael@0 2097 mStatements.GetCachedStatement(
michael@0 2098 NS_LITERAL_CSTRING("UPDATE moz_redirects SET hits = :hits, "
michael@0 2099 "last_hit = :now WHERE id = :redir;"));
michael@0 2100 if (!updateRedirect) {
michael@0 2101 return;
michael@0 2102 }
michael@0 2103 mozStorageStatementScoper scopedUpdate(updateRedirect);
michael@0 2104
michael@0 2105 rv = updateRedirect->BindInt32ByName(NS_LITERAL_CSTRING("hits"), hits + 1);
michael@0 2106 RETURN_IF_FAILED(rv);
michael@0 2107
michael@0 2108 rv = updateRedirect->BindInt64ByName(NS_LITERAL_CSTRING("now"), now);
michael@0 2109 RETURN_IF_FAILED(rv);
michael@0 2110
michael@0 2111 rv = updateRedirect->BindInt32ByName(NS_LITERAL_CSTRING("redir"), redirId);
michael@0 2112 RETURN_IF_FAILED(rv);
michael@0 2113
michael@0 2114 updateRedirect->Execute();
michael@0 2115 }
michael@0 2116 }
michael@0 2117
michael@0 2118 // Add information about a top-level load to our list of startup pages
michael@0 2119 void
michael@0 2120 Seer::LearnForStartup(const UriInfo &uri)
michael@0 2121 {
michael@0 2122 MOZ_ASSERT(!NS_IsMainThread(), "LearnForStartup called on main thread.");
michael@0 2123
michael@0 2124 if (NS_FAILED(EnsureInitStorage())) {
michael@0 2125 return;
michael@0 2126 }
michael@0 2127
michael@0 2128 nsCOMPtr<mozIStorageStatement> getPage = mStatements.GetCachedStatement(
michael@0 2129 NS_LITERAL_CSTRING("SELECT id, hits FROM moz_startup_pages WHERE "
michael@0 2130 "uri = :origin;"));
michael@0 2131 if (!getPage) {
michael@0 2132 return;
michael@0 2133 }
michael@0 2134 mozStorageStatementScoper scopedPage(getPage);
michael@0 2135 nsresult rv;
michael@0 2136
michael@0 2137 rv = getPage->BindUTF8StringByName(NS_LITERAL_CSTRING("origin"), uri.origin);
michael@0 2138 RETURN_IF_FAILED(rv);
michael@0 2139
michael@0 2140 bool hasRows;
michael@0 2141 rv = getPage->ExecuteStep(&hasRows);
michael@0 2142 RETURN_IF_FAILED(rv);
michael@0 2143
michael@0 2144 if (hasRows) {
michael@0 2145 // We've loaded this page on startup before
michael@0 2146 int32_t pageId, hitCount;
michael@0 2147
michael@0 2148 rv = getPage->GetInt32(0, &pageId);
michael@0 2149 RETURN_IF_FAILED(rv);
michael@0 2150
michael@0 2151 rv = getPage->GetInt32(1, &hitCount);
michael@0 2152 RETURN_IF_FAILED(rv);
michael@0 2153
michael@0 2154 nsCOMPtr<mozIStorageStatement> updatePage = mStatements.GetCachedStatement(
michael@0 2155 NS_LITERAL_CSTRING("UPDATE moz_startup_pages SET hits = :hit_count, "
michael@0 2156 "last_hit = :startup_time WHERE id = :page_id;"));
michael@0 2157 if (!updatePage) {
michael@0 2158 return;
michael@0 2159 }
michael@0 2160 mozStorageStatementScoper scopedUpdate(updatePage);
michael@0 2161
michael@0 2162 rv = updatePage->BindInt32ByName(NS_LITERAL_CSTRING("hit_count"),
michael@0 2163 hitCount + 1);
michael@0 2164 RETURN_IF_FAILED(rv);
michael@0 2165
michael@0 2166 rv = updatePage->BindInt64ByName(NS_LITERAL_CSTRING("startup_time"),
michael@0 2167 mStartupTime);
michael@0 2168 RETURN_IF_FAILED(rv);
michael@0 2169
michael@0 2170 rv = updatePage->BindInt32ByName(NS_LITERAL_CSTRING("page_id"), pageId);
michael@0 2171 RETURN_IF_FAILED(rv);
michael@0 2172
michael@0 2173 updatePage->Execute();
michael@0 2174 } else {
michael@0 2175 // New startup page
michael@0 2176 nsCOMPtr<mozIStorageStatement> addPage = mStatements.GetCachedStatement(
michael@0 2177 NS_LITERAL_CSTRING("INSERT INTO moz_startup_pages (uri, hits, "
michael@0 2178 "last_hit) VALUES (:origin, 1, :startup_time);"));
michael@0 2179 if (!addPage) {
michael@0 2180 return;
michael@0 2181 }
michael@0 2182 mozStorageStatementScoper scopedAdd(addPage);
michael@0 2183 rv = addPage->BindUTF8StringByName(NS_LITERAL_CSTRING("origin"),
michael@0 2184 uri.origin);
michael@0 2185 RETURN_IF_FAILED(rv);
michael@0 2186
michael@0 2187 rv = addPage->BindInt64ByName(NS_LITERAL_CSTRING("startup_time"),
michael@0 2188 mStartupTime);
michael@0 2189 RETURN_IF_FAILED(rv);
michael@0 2190
michael@0 2191 addPage->Execute();
michael@0 2192 }
michael@0 2193 }
michael@0 2194
michael@0 2195 // Called from the main thread to update the database
michael@0 2196 NS_IMETHODIMP
michael@0 2197 Seer::Learn(nsIURI *targetURI, nsIURI *sourceURI, SeerLearnReason reason,
michael@0 2198 nsILoadContext *loadContext)
michael@0 2199 {
michael@0 2200 MOZ_ASSERT(NS_IsMainThread(),
michael@0 2201 "Seer interface methods must be called on the main thread");
michael@0 2202
michael@0 2203 if (!mInitialized) {
michael@0 2204 return NS_ERROR_NOT_AVAILABLE;
michael@0 2205 }
michael@0 2206
michael@0 2207 if (!mEnabled) {
michael@0 2208 return NS_ERROR_NOT_AVAILABLE;
michael@0 2209 }
michael@0 2210
michael@0 2211 if (loadContext && loadContext->UsePrivateBrowsing()) {
michael@0 2212 // Don't want to do anything in PB mode
michael@0 2213 return NS_OK;
michael@0 2214 }
michael@0 2215
michael@0 2216 if (!IsNullOrHttp(targetURI) || !IsNullOrHttp(sourceURI)) {
michael@0 2217 return NS_ERROR_INVALID_ARG;
michael@0 2218 }
michael@0 2219
michael@0 2220 switch (reason) {
michael@0 2221 case nsINetworkSeer::LEARN_LOAD_TOPLEVEL:
michael@0 2222 case nsINetworkSeer::LEARN_STARTUP:
michael@0 2223 if (!targetURI || sourceURI) {
michael@0 2224 return NS_ERROR_INVALID_ARG;
michael@0 2225 }
michael@0 2226 break;
michael@0 2227 case nsINetworkSeer::LEARN_LOAD_REDIRECT:
michael@0 2228 case nsINetworkSeer::LEARN_LOAD_SUBRESOURCE:
michael@0 2229 if (!targetURI || !sourceURI) {
michael@0 2230 return NS_ERROR_INVALID_ARG;
michael@0 2231 }
michael@0 2232 break;
michael@0 2233 default:
michael@0 2234 return NS_ERROR_INVALID_ARG;
michael@0 2235 }
michael@0 2236
michael@0 2237 ++mAccumulators->mLearnAttempts;
michael@0 2238 nsresult rv = ReserveSpaceInQueue();
michael@0 2239 if (NS_FAILED(rv)) {
michael@0 2240 ++mAccumulators->mLearnFullQueue;
michael@0 2241 return NS_ERROR_NOT_AVAILABLE;
michael@0 2242 }
michael@0 2243
michael@0 2244 nsRefPtr<SeerLearnEvent> event = new SeerLearnEvent(targetURI, sourceURI,
michael@0 2245 reason);
michael@0 2246 return mIOThread->Dispatch(event, NS_DISPATCH_NORMAL);
michael@0 2247 }
michael@0 2248
michael@0 2249 // Runnable to clear out the database. Dispatched from the main thread to the
michael@0 2250 // seer thread
michael@0 2251 class SeerResetEvent : public nsRunnable
michael@0 2252 {
michael@0 2253 public:
michael@0 2254 SeerResetEvent()
michael@0 2255 { }
michael@0 2256
michael@0 2257 NS_IMETHOD Run() MOZ_OVERRIDE
michael@0 2258 {
michael@0 2259 MOZ_ASSERT(!NS_IsMainThread(), "Running reset on main thread");
michael@0 2260
michael@0 2261 gSeer->ResetInternal();
michael@0 2262
michael@0 2263 return NS_OK;
michael@0 2264 }
michael@0 2265 };
michael@0 2266
michael@0 2267 // Helper that actually does the database wipe.
michael@0 2268 void
michael@0 2269 Seer::ResetInternal()
michael@0 2270 {
michael@0 2271 MOZ_ASSERT(!NS_IsMainThread(), "Resetting db on main thread");
michael@0 2272
michael@0 2273 nsresult rv = EnsureInitStorage();
michael@0 2274 RETURN_IF_FAILED(rv);
michael@0 2275
michael@0 2276 mDB->ExecuteSimpleSQL(NS_LITERAL_CSTRING("DELETE FROM moz_redirects;"));
michael@0 2277 mDB->ExecuteSimpleSQL(NS_LITERAL_CSTRING("DELETE FROM moz_startup_pages;"));
michael@0 2278 mDB->ExecuteSimpleSQL(NS_LITERAL_CSTRING("DELETE FROM moz_startups;"));
michael@0 2279
michael@0 2280 // These cascade to moz_subresources and moz_subhosts, respectively.
michael@0 2281 mDB->ExecuteSimpleSQL(NS_LITERAL_CSTRING("DELETE FROM moz_pages;"));
michael@0 2282 mDB->ExecuteSimpleSQL(NS_LITERAL_CSTRING("DELETE FROM moz_hosts;"));
michael@0 2283
michael@0 2284 VacuumDatabase();
michael@0 2285
michael@0 2286 // Go ahead and ensure this is flushed to disk
michael@0 2287 CommitTransaction();
michael@0 2288 BeginTransaction();
michael@0 2289 }
michael@0 2290
michael@0 2291 // Called on the main thread to clear out all our knowledge. Tabula Rasa FTW!
michael@0 2292 NS_IMETHODIMP
michael@0 2293 Seer::Reset()
michael@0 2294 {
michael@0 2295 MOZ_ASSERT(NS_IsMainThread(),
michael@0 2296 "Seer interface methods must be called on the main thread");
michael@0 2297
michael@0 2298 if (!mInitialized) {
michael@0 2299 return NS_ERROR_NOT_AVAILABLE;
michael@0 2300 }
michael@0 2301
michael@0 2302 if (!mEnabled) {
michael@0 2303 return NS_OK;
michael@0 2304 }
michael@0 2305
michael@0 2306 nsRefPtr<SeerResetEvent> event = new SeerResetEvent();
michael@0 2307 return mIOThread->Dispatch(event, NS_DISPATCH_NORMAL);
michael@0 2308 }
michael@0 2309
michael@0 2310 class SeerCleanupEvent : public nsRunnable
michael@0 2311 {
michael@0 2312 public:
michael@0 2313 NS_IMETHOD Run() MOZ_OVERRIDE
michael@0 2314 {
michael@0 2315 gSeer->Cleanup();
michael@0 2316 gSeer->mCleanupScheduled = false;
michael@0 2317 return NS_OK;
michael@0 2318 }
michael@0 2319 };
michael@0 2320
michael@0 2321 // Returns the current size (in bytes) of the db file on disk
michael@0 2322 int64_t
michael@0 2323 Seer::GetDBFileSize()
michael@0 2324 {
michael@0 2325 MOZ_ASSERT(!NS_IsMainThread(), "GetDBFileSize called on main thread!");
michael@0 2326
michael@0 2327 nsresult rv = EnsureInitStorage();
michael@0 2328 if (NS_FAILED(rv)) {
michael@0 2329 SEER_LOG(("GetDBFileSize called without db available!"));
michael@0 2330 return 0;
michael@0 2331 }
michael@0 2332
michael@0 2333 CommitTransaction();
michael@0 2334
michael@0 2335 nsCOMPtr<mozIStorageStatement> countStmt = mStatements.GetCachedStatement(
michael@0 2336 NS_LITERAL_CSTRING("PRAGMA page_count;"));
michael@0 2337 if (!countStmt) {
michael@0 2338 return 0;
michael@0 2339 }
michael@0 2340 mozStorageStatementScoper scopedCount(countStmt);
michael@0 2341 bool hasRows;
michael@0 2342 rv = countStmt->ExecuteStep(&hasRows);
michael@0 2343 if (NS_FAILED(rv) || !hasRows) {
michael@0 2344 return 0;
michael@0 2345 }
michael@0 2346 int64_t pageCount;
michael@0 2347 rv = countStmt->GetInt64(0, &pageCount);
michael@0 2348 if (NS_FAILED(rv)) {
michael@0 2349 return 0;
michael@0 2350 }
michael@0 2351
michael@0 2352 nsCOMPtr<mozIStorageStatement> sizeStmt = mStatements.GetCachedStatement(
michael@0 2353 NS_LITERAL_CSTRING("PRAGMA page_size;"));
michael@0 2354 if (!sizeStmt) {
michael@0 2355 return 0;
michael@0 2356 }
michael@0 2357 mozStorageStatementScoper scopedSize(sizeStmt);
michael@0 2358 rv = sizeStmt->ExecuteStep(&hasRows);
michael@0 2359 if (NS_FAILED(rv) || !hasRows) {
michael@0 2360 return 0;
michael@0 2361 }
michael@0 2362 int64_t pageSize;
michael@0 2363 rv = sizeStmt->GetInt64(0, &pageSize);
michael@0 2364 if (NS_FAILED(rv)) {
michael@0 2365 return 0;
michael@0 2366 }
michael@0 2367
michael@0 2368 BeginTransaction();
michael@0 2369
michael@0 2370 return pageCount * pageSize;
michael@0 2371 }
michael@0 2372
michael@0 2373 // Returns the size (in bytes) that the db file will consume on disk AFTER we
michael@0 2374 // vacuum the db.
michael@0 2375 int64_t
michael@0 2376 Seer::GetDBFileSizeAfterVacuum()
michael@0 2377 {
michael@0 2378 MOZ_ASSERT(!NS_IsMainThread(), "GetDBFileSizeAfterVacuum called on main thread!");
michael@0 2379
michael@0 2380 CommitTransaction();
michael@0 2381
michael@0 2382 nsCOMPtr<mozIStorageStatement> countStmt = mStatements.GetCachedStatement(
michael@0 2383 NS_LITERAL_CSTRING("PRAGMA page_count;"));
michael@0 2384 if (!countStmt) {
michael@0 2385 return 0;
michael@0 2386 }
michael@0 2387 mozStorageStatementScoper scopedCount(countStmt);
michael@0 2388 bool hasRows;
michael@0 2389 nsresult rv = countStmt->ExecuteStep(&hasRows);
michael@0 2390 if (NS_FAILED(rv) || !hasRows) {
michael@0 2391 return 0;
michael@0 2392 }
michael@0 2393 int64_t pageCount;
michael@0 2394 rv = countStmt->GetInt64(0, &pageCount);
michael@0 2395 if (NS_FAILED(rv)) {
michael@0 2396 return 0;
michael@0 2397 }
michael@0 2398
michael@0 2399 nsCOMPtr<mozIStorageStatement> sizeStmt = mStatements.GetCachedStatement(
michael@0 2400 NS_LITERAL_CSTRING("PRAGMA page_size;"));
michael@0 2401 if (!sizeStmt) {
michael@0 2402 return 0;
michael@0 2403 }
michael@0 2404 mozStorageStatementScoper scopedSize(sizeStmt);
michael@0 2405 rv = sizeStmt->ExecuteStep(&hasRows);
michael@0 2406 if (NS_FAILED(rv) || !hasRows) {
michael@0 2407 return 0;
michael@0 2408 }
michael@0 2409 int64_t pageSize;
michael@0 2410 rv = sizeStmt->GetInt64(0, &pageSize);
michael@0 2411 if (NS_FAILED(rv)) {
michael@0 2412 return 0;
michael@0 2413 }
michael@0 2414
michael@0 2415 nsCOMPtr<mozIStorageStatement> freeStmt = mStatements.GetCachedStatement(
michael@0 2416 NS_LITERAL_CSTRING("PRAGMA freelist_count;"));
michael@0 2417 if (!freeStmt) {
michael@0 2418 return 0;
michael@0 2419 }
michael@0 2420 mozStorageStatementScoper scopedFree(freeStmt);
michael@0 2421 rv = freeStmt->ExecuteStep(&hasRows);
michael@0 2422 if (NS_FAILED(rv) || !hasRows) {
michael@0 2423 return 0;
michael@0 2424 }
michael@0 2425 int64_t freelistCount;
michael@0 2426 rv = freeStmt->GetInt64(0, &freelistCount);
michael@0 2427 if (NS_FAILED(rv)) {
michael@0 2428 return 0;
michael@0 2429 }
michael@0 2430
michael@0 2431 BeginTransaction();
michael@0 2432
michael@0 2433 return (pageCount - freelistCount) * pageSize;
michael@0 2434 }
michael@0 2435
michael@0 2436 void
michael@0 2437 Seer::MaybeScheduleCleanup()
michael@0 2438 {
michael@0 2439 MOZ_ASSERT(!NS_IsMainThread(), "MaybeScheduleCleanup called on main thread!");
michael@0 2440
michael@0 2441 // This is a little racy, but it's a nice little shutdown optimization if the
michael@0 2442 // race works out the right way.
michael@0 2443 if (!mInitialized) {
michael@0 2444 return;
michael@0 2445 }
michael@0 2446
michael@0 2447 if (mCleanupScheduled) {
michael@0 2448 Telemetry::Accumulate(Telemetry::SEER_CLEANUP_SCHEDULED, false);
michael@0 2449 return;
michael@0 2450 }
michael@0 2451
michael@0 2452 int64_t dbFileSize = GetDBFileSize();
michael@0 2453 if (dbFileSize < mMaxDBSize) {
michael@0 2454 Telemetry::Accumulate(Telemetry::SEER_CLEANUP_SCHEDULED, false);
michael@0 2455 return;
michael@0 2456 }
michael@0 2457
michael@0 2458 mCleanupScheduled = true;
michael@0 2459
michael@0 2460 nsRefPtr<SeerCleanupEvent> event = new SeerCleanupEvent();
michael@0 2461 nsresult rv = mIOThread->Dispatch(event, NS_DISPATCH_NORMAL);
michael@0 2462 if (NS_FAILED(rv)) {
michael@0 2463 mCleanupScheduled = false;
michael@0 2464 Telemetry::Accumulate(Telemetry::SEER_CLEANUP_SCHEDULED, false);
michael@0 2465 } else {
michael@0 2466 Telemetry::Accumulate(Telemetry::SEER_CLEANUP_SCHEDULED, true);
michael@0 2467 }
michael@0 2468 }
michael@0 2469
michael@0 2470 #ifndef ANDROID
michael@0 2471 static const long long CLEANUP_CUTOFF = ONE_MONTH;
michael@0 2472 #else
michael@0 2473 static const long long CLEANUP_CUTOFF = ONE_WEEK;
michael@0 2474 #endif
michael@0 2475
michael@0 2476 void
michael@0 2477 Seer::CleanupOrigins(PRTime now)
michael@0 2478 {
michael@0 2479 PRTime cutoff = now - CLEANUP_CUTOFF;
michael@0 2480
michael@0 2481 nsCOMPtr<mozIStorageStatement> deleteOrigins = mStatements.GetCachedStatement(
michael@0 2482 NS_LITERAL_CSTRING("DELETE FROM moz_hosts WHERE last_load <= :cutoff"));
michael@0 2483 if (!deleteOrigins) {
michael@0 2484 return;
michael@0 2485 }
michael@0 2486 mozStorageStatementScoper scopedOrigins(deleteOrigins);
michael@0 2487
michael@0 2488 nsresult rv = deleteOrigins->BindInt32ByName(NS_LITERAL_CSTRING("cutoff"),
michael@0 2489 cutoff);
michael@0 2490 RETURN_IF_FAILED(rv);
michael@0 2491
michael@0 2492 deleteOrigins->Execute();
michael@0 2493 }
michael@0 2494
michael@0 2495 void
michael@0 2496 Seer::CleanupStartupPages(PRTime now)
michael@0 2497 {
michael@0 2498 PRTime cutoff = now - ONE_WEEK;
michael@0 2499
michael@0 2500 nsCOMPtr<mozIStorageStatement> deletePages = mStatements.GetCachedStatement(
michael@0 2501 NS_LITERAL_CSTRING("DELETE FROM moz_startup_pages WHERE "
michael@0 2502 "last_hit <= :cutoff"));
michael@0 2503 if (!deletePages) {
michael@0 2504 return;
michael@0 2505 }
michael@0 2506 mozStorageStatementScoper scopedPages(deletePages);
michael@0 2507
michael@0 2508 nsresult rv = deletePages->BindInt32ByName(NS_LITERAL_CSTRING("cutoff"),
michael@0 2509 cutoff);
michael@0 2510 RETURN_IF_FAILED(rv);
michael@0 2511
michael@0 2512 deletePages->Execute();
michael@0 2513 }
michael@0 2514
michael@0 2515 int32_t
michael@0 2516 Seer::GetSubresourceCount()
michael@0 2517 {
michael@0 2518 nsCOMPtr<mozIStorageStatement> count = mStatements.GetCachedStatement(
michael@0 2519 NS_LITERAL_CSTRING("SELECT COUNT(id) FROM moz_subresources"));
michael@0 2520 if (!count) {
michael@0 2521 return 0;
michael@0 2522 }
michael@0 2523 mozStorageStatementScoper scopedCount(count);
michael@0 2524
michael@0 2525 bool hasRows;
michael@0 2526 nsresult rv = count->ExecuteStep(&hasRows);
michael@0 2527 if (NS_FAILED(rv) || !hasRows) {
michael@0 2528 return 0;
michael@0 2529 }
michael@0 2530
michael@0 2531 int32_t subresourceCount = 0;
michael@0 2532 count->GetInt32(0, &subresourceCount);
michael@0 2533
michael@0 2534 return subresourceCount;
michael@0 2535 }
michael@0 2536
michael@0 2537 void
michael@0 2538 Seer::Cleanup()
michael@0 2539 {
michael@0 2540 MOZ_ASSERT(!NS_IsMainThread(), "Seer::Cleanup called on main thread!");
michael@0 2541
michael@0 2542 nsresult rv = EnsureInitStorage();
michael@0 2543 if (NS_FAILED(rv)) {
michael@0 2544 return;
michael@0 2545 }
michael@0 2546
michael@0 2547 int64_t dbFileSize = GetDBFileSize();
michael@0 2548 float preservePercentage = static_cast<float>(mPreservePercentage) / 100.0;
michael@0 2549 int64_t evictionCutoff = static_cast<int64_t>(mMaxDBSize) * preservePercentage;
michael@0 2550 if (dbFileSize < evictionCutoff) {
michael@0 2551 return;
michael@0 2552 }
michael@0 2553
michael@0 2554 CommitTransaction();
michael@0 2555 BeginTransaction();
michael@0 2556
michael@0 2557 PRTime now = PR_Now();
michael@0 2558 if (mLastCleanupTime) {
michael@0 2559 Telemetry::Accumulate(Telemetry::SEER_CLEANUP_DELTA,
michael@0 2560 (now - mLastCleanupTime) / 1000);
michael@0 2561 }
michael@0 2562 mLastCleanupTime = now;
michael@0 2563
michael@0 2564 CleanupOrigins(now);
michael@0 2565 CleanupStartupPages(now);
michael@0 2566
michael@0 2567 dbFileSize = GetDBFileSizeAfterVacuum();
michael@0 2568 if (dbFileSize < evictionCutoff) {
michael@0 2569 // We've deleted enough stuff, time to free up the disk space and be on
michael@0 2570 // our way.
michael@0 2571 VacuumDatabase();
michael@0 2572 Telemetry::Accumulate(Telemetry::SEER_CLEANUP_SUCCEEDED, true);
michael@0 2573 Telemetry::Accumulate(Telemetry::SEER_CLEANUP_TIME,
michael@0 2574 (PR_Now() - mLastCleanupTime) / 1000);
michael@0 2575 return;
michael@0 2576 }
michael@0 2577
michael@0 2578 bool canDelete = true;
michael@0 2579 while (canDelete && (dbFileSize >= evictionCutoff)) {
michael@0 2580 int32_t subresourceCount = GetSubresourceCount();
michael@0 2581 if (!subresourceCount) {
michael@0 2582 canDelete = false;
michael@0 2583 break;
michael@0 2584 }
michael@0 2585
michael@0 2586 // DB size scales pretty much linearly with the number of rows in
michael@0 2587 // moz_subresources, so we can guess how many rows we need to delete pretty
michael@0 2588 // accurately.
michael@0 2589 float percentNeeded = static_cast<float>(dbFileSize - evictionCutoff) /
michael@0 2590 static_cast<float>(dbFileSize);
michael@0 2591
michael@0 2592 int32_t subresourcesToDelete = static_cast<int32_t>(percentNeeded * subresourceCount);
michael@0 2593 if (!subresourcesToDelete) {
michael@0 2594 // We're getting pretty close to nothing here, anyway, so we may as well
michael@0 2595 // just trash it all. This delete cascades to moz_subresources, as well.
michael@0 2596 rv = mDB->ExecuteSimpleSQL(NS_LITERAL_CSTRING("DELETE FROM moz_pages;"));
michael@0 2597 if (NS_FAILED(rv)) {
michael@0 2598 canDelete = false;
michael@0 2599 break;
michael@0 2600 }
michael@0 2601 } else {
michael@0 2602 nsCOMPtr<mozIStorageStatement> deleteStatement = mStatements.GetCachedStatement(
michael@0 2603 NS_LITERAL_CSTRING("DELETE FROM moz_subresources WHERE id IN "
michael@0 2604 "(SELECT id FROM moz_subresources ORDER BY "
michael@0 2605 "last_hit ASC LIMIT :limit);"));
michael@0 2606 if (!deleteStatement) {
michael@0 2607 canDelete = false;
michael@0 2608 break;
michael@0 2609 }
michael@0 2610 mozStorageStatementScoper scopedDelete(deleteStatement);
michael@0 2611
michael@0 2612 rv = deleteStatement->BindInt32ByName(NS_LITERAL_CSTRING("limit"),
michael@0 2613 subresourcesToDelete);
michael@0 2614 if (NS_FAILED(rv)) {
michael@0 2615 canDelete = false;
michael@0 2616 break;
michael@0 2617 }
michael@0 2618
michael@0 2619 rv = deleteStatement->Execute();
michael@0 2620 if (NS_FAILED(rv)) {
michael@0 2621 canDelete = false;
michael@0 2622 break;
michael@0 2623 }
michael@0 2624
michael@0 2625 // Now we clean up pages that no longer reference any subresources
michael@0 2626 rv = mDB->ExecuteSimpleSQL(
michael@0 2627 NS_LITERAL_CSTRING("DELETE FROM moz_pages WHERE id NOT IN "
michael@0 2628 "(SELECT DISTINCT(pid) FROM moz_subresources);"));
michael@0 2629 if (NS_FAILED(rv)) {
michael@0 2630 canDelete = false;
michael@0 2631 break;
michael@0 2632 }
michael@0 2633 }
michael@0 2634
michael@0 2635 if (canDelete) {
michael@0 2636 dbFileSize = GetDBFileSizeAfterVacuum();
michael@0 2637 }
michael@0 2638 }
michael@0 2639
michael@0 2640 if (!canDelete || (dbFileSize >= evictionCutoff)) {
michael@0 2641 // Last-ditch effort to free up space
michael@0 2642 ResetInternal();
michael@0 2643 Telemetry::Accumulate(Telemetry::SEER_CLEANUP_SUCCEEDED, false);
michael@0 2644 } else {
michael@0 2645 // We do this to actually free up the space on disk
michael@0 2646 VacuumDatabase();
michael@0 2647 Telemetry::Accumulate(Telemetry::SEER_CLEANUP_SUCCEEDED, true);
michael@0 2648 }
michael@0 2649 Telemetry::Accumulate(Telemetry::SEER_CLEANUP_TIME,
michael@0 2650 (PR_Now() - mLastCleanupTime) / 1000);
michael@0 2651 }
michael@0 2652
michael@0 2653 void
michael@0 2654 Seer::VacuumDatabase()
michael@0 2655 {
michael@0 2656 MOZ_ASSERT(!NS_IsMainThread(), "VacuumDatabase called on main thread!");
michael@0 2657
michael@0 2658 CommitTransaction();
michael@0 2659 mDB->ExecuteSimpleSQL(NS_LITERAL_CSTRING("VACUUM;"));
michael@0 2660 BeginTransaction();
michael@0 2661 }
michael@0 2662
michael@0 2663 #ifdef SEER_TESTS
michael@0 2664 class SeerPrepareForDnsTestEvent : public nsRunnable
michael@0 2665 {
michael@0 2666 public:
michael@0 2667 SeerPrepareForDnsTestEvent(int64_t timestamp, const char *uri)
michael@0 2668 :mTimestamp(timestamp)
michael@0 2669 ,mUri(uri)
michael@0 2670 { }
michael@0 2671
michael@0 2672 NS_IMETHOD Run() MOZ_OVERRIDE
michael@0 2673 {
michael@0 2674 MOZ_ASSERT(!NS_IsMainThread(), "Preparing for DNS Test on main thread!");
michael@0 2675 gSeer->PrepareForDnsTestInternal(mTimestamp, mUri);
michael@0 2676 return NS_OK;
michael@0 2677 }
michael@0 2678
michael@0 2679 private:
michael@0 2680 int64_t mTimestamp;
michael@0 2681 nsAutoCString mUri;
michael@0 2682 };
michael@0 2683
michael@0 2684 void
michael@0 2685 Seer::PrepareForDnsTestInternal(int64_t timestamp, const nsACString &uri)
michael@0 2686 {
michael@0 2687 nsCOMPtr<mozIStorageStatement> update = mStatements.GetCachedStatement(
michael@0 2688 NS_LITERAL_CSTRING("UPDATE moz_subresources SET last_hit = :timestamp, "
michael@0 2689 "hits = 2 WHERE uri = :uri;"));
michael@0 2690 if (!update) {
michael@0 2691 return;
michael@0 2692 }
michael@0 2693 mozStorageStatementScoper scopedUpdate(update);
michael@0 2694
michael@0 2695 nsresult rv = update->BindInt64ByName(NS_LITERAL_CSTRING("timestamp"),
michael@0 2696 timestamp);
michael@0 2697 RETURN_IF_FAILED(rv);
michael@0 2698
michael@0 2699 rv = update->BindUTF8StringByName(NS_LITERAL_CSTRING("uri"), uri);
michael@0 2700 RETURN_IF_FAILED(rv);
michael@0 2701
michael@0 2702 update->Execute();
michael@0 2703 }
michael@0 2704 #endif
michael@0 2705
michael@0 2706 NS_IMETHODIMP
michael@0 2707 Seer::PrepareForDnsTest(int64_t timestamp, const char *uri)
michael@0 2708 {
michael@0 2709 #ifdef SEER_TESTS
michael@0 2710 MOZ_ASSERT(NS_IsMainThread(),
michael@0 2711 "Seer interface methods must be called on the main thread");
michael@0 2712
michael@0 2713 if (!mInitialized) {
michael@0 2714 return NS_ERROR_NOT_AVAILABLE;
michael@0 2715 }
michael@0 2716
michael@0 2717 nsRefPtr<SeerPrepareForDnsTestEvent> event =
michael@0 2718 new SeerPrepareForDnsTestEvent(timestamp, uri);
michael@0 2719 return mIOThread->Dispatch(event, NS_DISPATCH_NORMAL);
michael@0 2720 #else
michael@0 2721 return NS_ERROR_NOT_IMPLEMENTED;
michael@0 2722 #endif
michael@0 2723 }
michael@0 2724
michael@0 2725 // Helper functions to make using the seer easier from native code
michael@0 2726
michael@0 2727 static nsresult
michael@0 2728 EnsureGlobalSeer(nsINetworkSeer **aSeer)
michael@0 2729 {
michael@0 2730 nsresult rv;
michael@0 2731 nsCOMPtr<nsINetworkSeer> seer = do_GetService("@mozilla.org/network/seer;1",
michael@0 2732 &rv);
michael@0 2733 NS_ENSURE_SUCCESS(rv, rv);
michael@0 2734
michael@0 2735 NS_IF_ADDREF(*aSeer = seer);
michael@0 2736 return NS_OK;
michael@0 2737 }
michael@0 2738
michael@0 2739 nsresult
michael@0 2740 SeerPredict(nsIURI *targetURI, nsIURI *sourceURI, SeerPredictReason reason,
michael@0 2741 nsILoadContext *loadContext, nsINetworkSeerVerifier *verifier)
michael@0 2742 {
michael@0 2743 if (!IsNullOrHttp(targetURI) || !IsNullOrHttp(sourceURI)) {
michael@0 2744 return NS_OK;
michael@0 2745 }
michael@0 2746
michael@0 2747 nsCOMPtr<nsINetworkSeer> seer;
michael@0 2748 nsresult rv = EnsureGlobalSeer(getter_AddRefs(seer));
michael@0 2749 NS_ENSURE_SUCCESS(rv, rv);
michael@0 2750
michael@0 2751 return seer->Predict(targetURI, sourceURI, reason, loadContext, verifier);
michael@0 2752 }
michael@0 2753
michael@0 2754 nsresult
michael@0 2755 SeerLearn(nsIURI *targetURI, nsIURI *sourceURI, SeerLearnReason reason,
michael@0 2756 nsILoadContext *loadContext)
michael@0 2757 {
michael@0 2758 if (!IsNullOrHttp(targetURI) || !IsNullOrHttp(sourceURI)) {
michael@0 2759 return NS_OK;
michael@0 2760 }
michael@0 2761
michael@0 2762 nsCOMPtr<nsINetworkSeer> seer;
michael@0 2763 nsresult rv = EnsureGlobalSeer(getter_AddRefs(seer));
michael@0 2764 NS_ENSURE_SUCCESS(rv, rv);
michael@0 2765
michael@0 2766 return seer->Learn(targetURI, sourceURI, reason, loadContext);
michael@0 2767 }
michael@0 2768
michael@0 2769 nsresult
michael@0 2770 SeerLearn(nsIURI *targetURI, nsIURI *sourceURI, SeerLearnReason reason,
michael@0 2771 nsILoadGroup *loadGroup)
michael@0 2772 {
michael@0 2773 if (!IsNullOrHttp(targetURI) || !IsNullOrHttp(sourceURI)) {
michael@0 2774 return NS_OK;
michael@0 2775 }
michael@0 2776
michael@0 2777 nsCOMPtr<nsINetworkSeer> seer;
michael@0 2778 nsresult rv = EnsureGlobalSeer(getter_AddRefs(seer));
michael@0 2779 NS_ENSURE_SUCCESS(rv, rv);
michael@0 2780
michael@0 2781 nsCOMPtr<nsILoadContext> loadContext;
michael@0 2782
michael@0 2783 if (loadGroup) {
michael@0 2784 nsCOMPtr<nsIInterfaceRequestor> callbacks;
michael@0 2785 loadGroup->GetNotificationCallbacks(getter_AddRefs(callbacks));
michael@0 2786 if (callbacks) {
michael@0 2787 loadContext = do_GetInterface(callbacks);
michael@0 2788 }
michael@0 2789 }
michael@0 2790
michael@0 2791 return seer->Learn(targetURI, sourceURI, reason, loadContext);
michael@0 2792 }
michael@0 2793
michael@0 2794 nsresult
michael@0 2795 SeerLearn(nsIURI *targetURI, nsIURI *sourceURI, SeerLearnReason reason,
michael@0 2796 nsIDocument *document)
michael@0 2797 {
michael@0 2798 if (!IsNullOrHttp(targetURI) || !IsNullOrHttp(sourceURI)) {
michael@0 2799 return NS_OK;
michael@0 2800 }
michael@0 2801
michael@0 2802 nsCOMPtr<nsINetworkSeer> seer;
michael@0 2803 nsresult rv = EnsureGlobalSeer(getter_AddRefs(seer));
michael@0 2804 NS_ENSURE_SUCCESS(rv, rv);
michael@0 2805
michael@0 2806 nsCOMPtr<nsILoadContext> loadContext;
michael@0 2807
michael@0 2808 if (document) {
michael@0 2809 loadContext = document->GetLoadContext();
michael@0 2810 }
michael@0 2811
michael@0 2812 return seer->Learn(targetURI, sourceURI, reason, loadContext);
michael@0 2813 }
michael@0 2814
michael@0 2815 nsresult
michael@0 2816 SeerLearnRedirect(nsIURI *targetURI, nsIChannel *channel,
michael@0 2817 nsILoadContext *loadContext)
michael@0 2818 {
michael@0 2819 nsCOMPtr<nsIURI> sourceURI;
michael@0 2820 nsresult rv = channel->GetOriginalURI(getter_AddRefs(sourceURI));
michael@0 2821 NS_ENSURE_SUCCESS(rv, rv);
michael@0 2822
michael@0 2823 bool sameUri;
michael@0 2824 rv = targetURI->Equals(sourceURI, &sameUri);
michael@0 2825 NS_ENSURE_SUCCESS(rv, rv);
michael@0 2826
michael@0 2827 if (sameUri) {
michael@0 2828 return NS_OK;
michael@0 2829 }
michael@0 2830
michael@0 2831 if (!IsNullOrHttp(targetURI) || !IsNullOrHttp(sourceURI)) {
michael@0 2832 return NS_OK;
michael@0 2833 }
michael@0 2834
michael@0 2835 nsCOMPtr<nsINetworkSeer> seer;
michael@0 2836 rv = EnsureGlobalSeer(getter_AddRefs(seer));
michael@0 2837 NS_ENSURE_SUCCESS(rv, rv);
michael@0 2838
michael@0 2839 return seer->Learn(targetURI, sourceURI,
michael@0 2840 nsINetworkSeer::LEARN_LOAD_REDIRECT, loadContext);
michael@0 2841 }
michael@0 2842
michael@0 2843 } // ::mozilla::net
michael@0 2844 } // ::mozilla

mercurial