michael@0: /* vim: set ts=2 sts=2 et sw=2: */ michael@0: /* This Source Code Form is subject to the terms of the Mozilla Public michael@0: * License, v. 2.0. If a copy of the MPL was not distributed with this michael@0: * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ michael@0: michael@0: #include michael@0: michael@0: #include "Seer.h" michael@0: michael@0: #include "nsAppDirectoryServiceDefs.h" michael@0: #include "nsICancelable.h" michael@0: #include "nsIChannel.h" michael@0: #include "nsIDNSListener.h" michael@0: #include "nsIDNSService.h" michael@0: #include "nsIDocument.h" michael@0: #include "nsIFile.h" michael@0: #include "nsILoadContext.h" michael@0: #include "nsILoadGroup.h" michael@0: #include "nsINetworkSeerVerifier.h" michael@0: #include "nsIObserverService.h" michael@0: #include "nsIPrefBranch.h" michael@0: #include "nsIPrefService.h" michael@0: #include "nsISpeculativeConnect.h" michael@0: #include "nsITimer.h" michael@0: #include "nsIURI.h" michael@0: #include "nsNetUtil.h" michael@0: #include "nsServiceManagerUtils.h" michael@0: #include "nsTArray.h" michael@0: #include "nsThreadUtils.h" michael@0: #ifdef MOZ_NUWA_PROCESS michael@0: #include "ipc/Nuwa.h" michael@0: #endif michael@0: #include "prlog.h" michael@0: michael@0: #include "mozIStorageConnection.h" michael@0: #include "mozIStorageService.h" michael@0: #include "mozIStorageStatement.h" michael@0: #include "mozStorageHelper.h" michael@0: michael@0: #include "mozilla/Preferences.h" michael@0: #include "mozilla/storage.h" michael@0: #include "mozilla/Telemetry.h" michael@0: michael@0: #if defined(ANDROID) && !defined(MOZ_WIDGET_GONK) michael@0: #include "nsIPropertyBag2.h" michael@0: static const int32_t ANDROID_23_VERSION = 10; michael@0: #endif michael@0: michael@0: using namespace mozilla; michael@0: michael@0: namespace mozilla { michael@0: namespace net { michael@0: michael@0: #define RETURN_IF_FAILED(_rv) \ michael@0: do { \ michael@0: if (NS_FAILED(_rv)) { \ michael@0: return; \ michael@0: } \ michael@0: } while (0) michael@0: michael@0: const char SEER_ENABLED_PREF[] = "network.seer.enabled"; michael@0: const char SEER_SSL_HOVER_PREF[] = "network.seer.enable-hover-on-ssl"; michael@0: michael@0: const char SEER_PAGE_DELTA_DAY_PREF[] = "network.seer.page-degradation.day"; michael@0: const int SEER_PAGE_DELTA_DAY_DEFAULT = 0; michael@0: const char SEER_PAGE_DELTA_WEEK_PREF[] = "network.seer.page-degradation.week"; michael@0: const int SEER_PAGE_DELTA_WEEK_DEFAULT = 5; michael@0: const char SEER_PAGE_DELTA_MONTH_PREF[] = "network.seer.page-degradation.month"; michael@0: const int SEER_PAGE_DELTA_MONTH_DEFAULT = 10; michael@0: const char SEER_PAGE_DELTA_YEAR_PREF[] = "network.seer.page-degradation.year"; michael@0: const int SEER_PAGE_DELTA_YEAR_DEFAULT = 25; michael@0: const char SEER_PAGE_DELTA_MAX_PREF[] = "network.seer.page-degradation.max"; michael@0: const int SEER_PAGE_DELTA_MAX_DEFAULT = 50; michael@0: const char SEER_SUB_DELTA_DAY_PREF[] = michael@0: "network.seer.subresource-degradation.day"; michael@0: const int SEER_SUB_DELTA_DAY_DEFAULT = 1; michael@0: const char SEER_SUB_DELTA_WEEK_PREF[] = michael@0: "network.seer.subresource-degradation.week"; michael@0: const int SEER_SUB_DELTA_WEEK_DEFAULT = 10; michael@0: const char SEER_SUB_DELTA_MONTH_PREF[] = michael@0: "network.seer.subresource-degradation.month"; michael@0: const int SEER_SUB_DELTA_MONTH_DEFAULT = 25; michael@0: const char SEER_SUB_DELTA_YEAR_PREF[] = michael@0: "network.seer.subresource-degradation.year"; michael@0: const int SEER_SUB_DELTA_YEAR_DEFAULT = 50; michael@0: const char SEER_SUB_DELTA_MAX_PREF[] = michael@0: "network.seer.subresource-degradation.max"; michael@0: const int SEER_SUB_DELTA_MAX_DEFAULT = 100; michael@0: michael@0: const char SEER_PRECONNECT_MIN_PREF[] = michael@0: "network.seer.preconnect-min-confidence"; michael@0: const int PRECONNECT_MIN_DEFAULT = 90; michael@0: const char SEER_PRERESOLVE_MIN_PREF[] = michael@0: "network.seer.preresolve-min-confidence"; michael@0: const int PRERESOLVE_MIN_DEFAULT = 60; michael@0: const char SEER_REDIRECT_LIKELY_PREF[] = michael@0: "network.seer.redirect-likely-confidence"; michael@0: const int REDIRECT_LIKELY_DEFAULT = 75; michael@0: michael@0: const char SEER_MAX_QUEUE_SIZE_PREF[] = "network.seer.max-queue-size"; michael@0: const uint32_t SEER_MAX_QUEUE_SIZE_DEFAULT = 50; michael@0: michael@0: const char SEER_MAX_DB_SIZE_PREF[] = "network.seer.max-db-size"; michael@0: const int32_t SEER_MAX_DB_SIZE_DEFAULT_BYTES = 150 * 1024 * 1024; michael@0: const char SEER_PRESERVE_PERCENTAGE_PREF[] = "network.seer.preserve"; michael@0: const int32_t SEER_PRESERVE_PERCENTAGE_DEFAULT = 80; michael@0: michael@0: // All these time values are in usec michael@0: const long long ONE_DAY = 86400LL * 1000000LL; michael@0: const long long ONE_WEEK = 7LL * ONE_DAY; michael@0: const long long ONE_MONTH = 30LL * ONE_DAY; michael@0: const long long ONE_YEAR = 365LL * ONE_DAY; michael@0: michael@0: const long STARTUP_WINDOW = 5L * 60L * 1000000L; // 5min michael@0: michael@0: // Version for the database schema michael@0: static const int32_t SEER_SCHEMA_VERSION = 1; michael@0: michael@0: struct SeerTelemetryAccumulators { michael@0: Telemetry::AutoCounter mPredictAttempts; michael@0: Telemetry::AutoCounter mLearnAttempts; michael@0: Telemetry::AutoCounter mPredictFullQueue; michael@0: Telemetry::AutoCounter mLearnFullQueue; michael@0: Telemetry::AutoCounter mTotalPredictions; michael@0: Telemetry::AutoCounter mTotalPreconnects; michael@0: Telemetry::AutoCounter mTotalPreresolves; michael@0: Telemetry::AutoCounter mPredictionsCalculated; michael@0: Telemetry::AutoCounter mLoadCountZeroes; michael@0: Telemetry::AutoCounter mLoadCountOverflows; michael@0: Telemetry::AutoCounter mStartupCountZeroes; michael@0: Telemetry::AutoCounter mStartupCountOverflows; michael@0: }; michael@0: michael@0: // Listener for the speculative DNS requests we'll fire off, which just ignores michael@0: // the result (since we're just trying to warm the cache). This also exists to michael@0: // reduce round-trips to the main thread, by being something threadsafe the Seer michael@0: // can use. michael@0: michael@0: class SeerDNSListener : public nsIDNSListener michael@0: { michael@0: public: michael@0: NS_DECL_THREADSAFE_ISUPPORTS michael@0: NS_DECL_NSIDNSLISTENER michael@0: michael@0: SeerDNSListener() michael@0: { } michael@0: michael@0: virtual ~SeerDNSListener() michael@0: { } michael@0: }; michael@0: michael@0: NS_IMPL_ISUPPORTS(SeerDNSListener, nsIDNSListener); michael@0: michael@0: NS_IMETHODIMP michael@0: SeerDNSListener::OnLookupComplete(nsICancelable *request, michael@0: nsIDNSRecord *rec, michael@0: nsresult status) michael@0: { michael@0: return NS_OK; michael@0: } michael@0: michael@0: // Are you ready for the fun part? Because here comes the fun part. The seer, michael@0: // which will do awesome stuff as you browse to make your browsing experience michael@0: // faster. michael@0: michael@0: static Seer *gSeer = nullptr; michael@0: michael@0: #if defined(PR_LOGGING) michael@0: static PRLogModuleInfo *gSeerLog = nullptr; michael@0: #define SEER_LOG(args) PR_LOG(gSeerLog, 4, args) michael@0: #else michael@0: #define SEER_LOG(args) michael@0: #endif michael@0: michael@0: NS_IMPL_ISUPPORTS(Seer, michael@0: nsINetworkSeer, michael@0: nsIObserver, michael@0: nsISpeculativeConnectionOverrider, michael@0: nsIInterfaceRequestor) michael@0: michael@0: Seer::Seer() michael@0: :mInitialized(false) michael@0: ,mEnabled(true) michael@0: ,mEnableHoverOnSSL(false) michael@0: ,mPageDegradationDay(SEER_PAGE_DELTA_DAY_DEFAULT) michael@0: ,mPageDegradationWeek(SEER_PAGE_DELTA_WEEK_DEFAULT) michael@0: ,mPageDegradationMonth(SEER_PAGE_DELTA_MONTH_DEFAULT) michael@0: ,mPageDegradationYear(SEER_PAGE_DELTA_YEAR_DEFAULT) michael@0: ,mPageDegradationMax(SEER_PAGE_DELTA_MAX_DEFAULT) michael@0: ,mSubresourceDegradationDay(SEER_SUB_DELTA_DAY_DEFAULT) michael@0: ,mSubresourceDegradationWeek(SEER_SUB_DELTA_WEEK_DEFAULT) michael@0: ,mSubresourceDegradationMonth(SEER_SUB_DELTA_MONTH_DEFAULT) michael@0: ,mSubresourceDegradationYear(SEER_SUB_DELTA_YEAR_DEFAULT) michael@0: ,mSubresourceDegradationMax(SEER_SUB_DELTA_MAX_DEFAULT) michael@0: ,mPreconnectMinConfidence(PRECONNECT_MIN_DEFAULT) michael@0: ,mPreresolveMinConfidence(PRERESOLVE_MIN_DEFAULT) michael@0: ,mRedirectLikelyConfidence(REDIRECT_LIKELY_DEFAULT) michael@0: ,mMaxQueueSize(SEER_MAX_QUEUE_SIZE_DEFAULT) michael@0: ,mStatements(mDB) michael@0: ,mLastStartupTime(0) michael@0: ,mStartupCount(0) michael@0: ,mQueueSize(0) michael@0: ,mQueueSizeLock("Seer.mQueueSizeLock") michael@0: ,mCleanupScheduled(false) michael@0: ,mMaxDBSize(SEER_MAX_DB_SIZE_DEFAULT_BYTES) michael@0: ,mPreservePercentage(SEER_PRESERVE_PERCENTAGE_DEFAULT) michael@0: ,mLastCleanupTime(0) michael@0: { michael@0: #if defined(PR_LOGGING) michael@0: gSeerLog = PR_NewLogModule("NetworkSeer"); michael@0: #endif michael@0: michael@0: MOZ_ASSERT(!gSeer, "multiple Seer instances!"); michael@0: gSeer = this; michael@0: } michael@0: michael@0: Seer::~Seer() michael@0: { michael@0: if (mInitialized) michael@0: Shutdown(); michael@0: michael@0: RemoveObserver(); michael@0: michael@0: gSeer = nullptr; michael@0: } michael@0: michael@0: // Seer::nsIObserver michael@0: michael@0: nsresult michael@0: Seer::InstallObserver() michael@0: { michael@0: MOZ_ASSERT(NS_IsMainThread(), "Installing observer off main thread"); michael@0: michael@0: nsresult rv = NS_OK; michael@0: nsCOMPtr obs = michael@0: mozilla::services::GetObserverService(); michael@0: if (!obs) { michael@0: return NS_ERROR_NOT_AVAILABLE; michael@0: } michael@0: michael@0: rv = obs->AddObserver(this, NS_XPCOM_SHUTDOWN_OBSERVER_ID, false); michael@0: NS_ENSURE_SUCCESS(rv, rv); michael@0: michael@0: nsCOMPtr prefs = do_GetService(NS_PREFSERVICE_CONTRACTID); michael@0: if (!prefs) { michael@0: return NS_ERROR_NOT_AVAILABLE; michael@0: } michael@0: michael@0: Preferences::AddBoolVarCache(&mEnabled, SEER_ENABLED_PREF, true); michael@0: Preferences::AddBoolVarCache(&mEnableHoverOnSSL, SEER_SSL_HOVER_PREF, false); michael@0: Preferences::AddIntVarCache(&mPageDegradationDay, SEER_PAGE_DELTA_DAY_PREF, michael@0: SEER_PAGE_DELTA_DAY_DEFAULT); michael@0: Preferences::AddIntVarCache(&mPageDegradationWeek, SEER_PAGE_DELTA_WEEK_PREF, michael@0: SEER_PAGE_DELTA_WEEK_DEFAULT); michael@0: Preferences::AddIntVarCache(&mPageDegradationMonth, michael@0: SEER_PAGE_DELTA_MONTH_PREF, michael@0: SEER_PAGE_DELTA_MONTH_DEFAULT); michael@0: Preferences::AddIntVarCache(&mPageDegradationYear, SEER_PAGE_DELTA_YEAR_PREF, michael@0: SEER_PAGE_DELTA_YEAR_DEFAULT); michael@0: Preferences::AddIntVarCache(&mPageDegradationMax, SEER_PAGE_DELTA_MAX_PREF, michael@0: SEER_PAGE_DELTA_MAX_DEFAULT); michael@0: michael@0: Preferences::AddIntVarCache(&mSubresourceDegradationDay, michael@0: SEER_SUB_DELTA_DAY_PREF, michael@0: SEER_SUB_DELTA_DAY_DEFAULT); michael@0: Preferences::AddIntVarCache(&mSubresourceDegradationWeek, michael@0: SEER_SUB_DELTA_WEEK_PREF, michael@0: SEER_SUB_DELTA_WEEK_DEFAULT); michael@0: Preferences::AddIntVarCache(&mSubresourceDegradationMonth, michael@0: SEER_SUB_DELTA_MONTH_PREF, michael@0: SEER_SUB_DELTA_MONTH_DEFAULT); michael@0: Preferences::AddIntVarCache(&mSubresourceDegradationYear, michael@0: SEER_SUB_DELTA_YEAR_PREF, michael@0: SEER_SUB_DELTA_YEAR_DEFAULT); michael@0: Preferences::AddIntVarCache(&mSubresourceDegradationMax, michael@0: SEER_SUB_DELTA_MAX_PREF, michael@0: SEER_SUB_DELTA_MAX_DEFAULT); michael@0: michael@0: Preferences::AddIntVarCache(&mPreconnectMinConfidence, michael@0: SEER_PRECONNECT_MIN_PREF, michael@0: PRECONNECT_MIN_DEFAULT); michael@0: Preferences::AddIntVarCache(&mPreresolveMinConfidence, michael@0: SEER_PRERESOLVE_MIN_PREF, michael@0: PRERESOLVE_MIN_DEFAULT); michael@0: Preferences::AddIntVarCache(&mRedirectLikelyConfidence, michael@0: SEER_REDIRECT_LIKELY_PREF, michael@0: REDIRECT_LIKELY_DEFAULT); michael@0: michael@0: Preferences::AddIntVarCache(&mMaxQueueSize, SEER_MAX_QUEUE_SIZE_PREF, michael@0: SEER_MAX_QUEUE_SIZE_DEFAULT); michael@0: michael@0: Preferences::AddIntVarCache(&mMaxDBSize, SEER_MAX_DB_SIZE_PREF, michael@0: SEER_MAX_DB_SIZE_DEFAULT_BYTES); michael@0: Preferences::AddIntVarCache(&mPreservePercentage, michael@0: SEER_PRESERVE_PERCENTAGE_PREF, michael@0: SEER_PRESERVE_PERCENTAGE_DEFAULT); michael@0: michael@0: return rv; michael@0: } michael@0: michael@0: void michael@0: Seer::RemoveObserver() michael@0: { michael@0: MOZ_ASSERT(NS_IsMainThread(), "Removing observer off main thread"); michael@0: michael@0: nsCOMPtr obs = michael@0: mozilla::services::GetObserverService(); michael@0: if (obs) { michael@0: obs->RemoveObserver(this, NS_XPCOM_SHUTDOWN_OBSERVER_ID); michael@0: } michael@0: } michael@0: michael@0: static const uint32_t COMMIT_TIMER_DELTA_MS = 5 * 1000; michael@0: michael@0: class SeerCommitTimerInitEvent : public nsRunnable michael@0: { michael@0: public: michael@0: NS_IMETHOD Run() MOZ_OVERRIDE michael@0: { michael@0: nsresult rv = NS_OK; michael@0: michael@0: if (!gSeer->mCommitTimer) { michael@0: gSeer->mCommitTimer = do_CreateInstance(NS_TIMER_CONTRACTID, &rv); michael@0: } else { michael@0: gSeer->mCommitTimer->Cancel(); michael@0: } michael@0: if (NS_SUCCEEDED(rv)) { michael@0: gSeer->mCommitTimer->Init(gSeer, COMMIT_TIMER_DELTA_MS, michael@0: nsITimer::TYPE_ONE_SHOT); michael@0: } michael@0: michael@0: return NS_OK; michael@0: } michael@0: }; michael@0: michael@0: class SeerNewTransactionEvent : public nsRunnable michael@0: { michael@0: NS_IMETHODIMP Run() MOZ_OVERRIDE michael@0: { michael@0: gSeer->CommitTransaction(); michael@0: gSeer->BeginTransaction(); michael@0: gSeer->MaybeScheduleCleanup(); michael@0: nsRefPtr event = new SeerCommitTimerInitEvent(); michael@0: NS_DispatchToMainThread(event); michael@0: return NS_OK; michael@0: } michael@0: }; michael@0: michael@0: NS_IMETHODIMP michael@0: Seer::Observe(nsISupports *subject, const char *topic, michael@0: const char16_t *data_unicode) michael@0: { michael@0: nsresult rv = NS_OK; michael@0: MOZ_ASSERT(NS_IsMainThread(), "Seer observing something off main thread!"); michael@0: michael@0: if (!strcmp(NS_XPCOM_SHUTDOWN_OBSERVER_ID, topic)) { michael@0: Shutdown(); michael@0: } else if (!strcmp(NS_TIMER_CALLBACK_TOPIC, topic)) { michael@0: if (mInitialized) { // Can't access io thread if we're not initialized! michael@0: nsRefPtr event = new SeerNewTransactionEvent(); michael@0: mIOThread->Dispatch(event, NS_DISPATCH_NORMAL); michael@0: } michael@0: } michael@0: michael@0: return rv; michael@0: } michael@0: michael@0: // Seer::nsISpeculativeConnectionOverrider michael@0: michael@0: NS_IMETHODIMP michael@0: Seer::GetIgnoreIdle(bool *ignoreIdle) michael@0: { michael@0: *ignoreIdle = true; michael@0: return NS_OK; michael@0: } michael@0: michael@0: NS_IMETHODIMP michael@0: Seer::GetIgnorePossibleSpdyConnections(bool *ignorePossibleSpdyConnections) michael@0: { michael@0: *ignorePossibleSpdyConnections = true; michael@0: return NS_OK; michael@0: } michael@0: michael@0: NS_IMETHODIMP michael@0: Seer::GetParallelSpeculativeConnectLimit( michael@0: uint32_t *parallelSpeculativeConnectLimit) michael@0: { michael@0: *parallelSpeculativeConnectLimit = 6; michael@0: return NS_OK; michael@0: } michael@0: michael@0: // Seer::nsIInterfaceRequestor michael@0: michael@0: NS_IMETHODIMP michael@0: Seer::GetInterface(const nsIID &iid, void **result) michael@0: { michael@0: return QueryInterface(iid, result); michael@0: } michael@0: michael@0: #ifdef MOZ_NUWA_PROCESS michael@0: class NuwaMarkSeerThreadRunner : public nsRunnable michael@0: { michael@0: NS_IMETHODIMP Run() MOZ_OVERRIDE michael@0: { michael@0: if (IsNuwaProcess()) { michael@0: NS_ASSERTION(NuwaMarkCurrentThread != nullptr, michael@0: "NuwaMarkCurrentThread is undefined!"); michael@0: NuwaMarkCurrentThread(nullptr, nullptr); michael@0: } michael@0: return NS_OK; michael@0: } michael@0: }; michael@0: #endif michael@0: michael@0: // Seer::nsINetworkSeer michael@0: michael@0: nsresult michael@0: Seer::Init() michael@0: { michael@0: if (!NS_IsMainThread()) { michael@0: MOZ_ASSERT(false, "Seer::Init called off the main thread!"); michael@0: return NS_ERROR_UNEXPECTED; michael@0: } michael@0: michael@0: nsresult rv = NS_OK; michael@0: michael@0: #if defined(ANDROID) && !defined(MOZ_WIDGET_GONK) michael@0: // This is an ugly hack to disable the seer on android < 2.3, as it doesn't michael@0: // play nicely with those android versions, at least on our infra. Causes michael@0: // timeouts in reftests. See bug 881804 comment 86. michael@0: nsCOMPtr infoService = michael@0: do_GetService("@mozilla.org/system-info;1"); michael@0: if (infoService) { michael@0: int32_t androidVersion = -1; michael@0: rv = infoService->GetPropertyAsInt32(NS_LITERAL_STRING("version"), michael@0: &androidVersion); michael@0: if (NS_SUCCEEDED(rv) && (androidVersion < ANDROID_23_VERSION)) { michael@0: return NS_ERROR_NOT_AVAILABLE; michael@0: } michael@0: } michael@0: #endif michael@0: michael@0: mStartupTime = PR_Now(); michael@0: michael@0: mAccumulators = new SeerTelemetryAccumulators(); michael@0: michael@0: rv = InstallObserver(); michael@0: NS_ENSURE_SUCCESS(rv, rv); michael@0: michael@0: if (!mDNSListener) { michael@0: mDNSListener = new SeerDNSListener(); michael@0: } michael@0: michael@0: rv = NS_NewNamedThread("Network Seer", getter_AddRefs(mIOThread)); michael@0: NS_ENSURE_SUCCESS(rv, rv); michael@0: michael@0: #ifdef MOZ_NUWA_PROCESS michael@0: nsCOMPtr runner = new NuwaMarkSeerThreadRunner(); michael@0: mIOThread->Dispatch(runner, NS_DISPATCH_NORMAL); michael@0: #endif michael@0: michael@0: mSpeculativeService = do_GetService("@mozilla.org/network/io-service;1", &rv); michael@0: NS_ENSURE_SUCCESS(rv, rv); michael@0: michael@0: mDnsService = do_GetService("@mozilla.org/network/dns-service;1", &rv); michael@0: NS_ENSURE_SUCCESS(rv, rv); michael@0: michael@0: mStorageService = do_GetService("@mozilla.org/storage/service;1", &rv); michael@0: NS_ENSURE_SUCCESS(rv, rv); michael@0: michael@0: rv = NS_GetSpecialDirectory(NS_APP_USER_PROFILE_50_DIR, michael@0: getter_AddRefs(mDBFile)); michael@0: NS_ENSURE_SUCCESS(rv, rv); michael@0: rv = mDBFile->AppendNative(NS_LITERAL_CSTRING("netpredictions.sqlite")); michael@0: NS_ENSURE_SUCCESS(rv, rv); michael@0: michael@0: mInitialized = true; michael@0: michael@0: return rv; michael@0: } michael@0: michael@0: void michael@0: Seer::CheckForAndDeleteOldDBFile() michael@0: { michael@0: nsCOMPtr oldDBFile; michael@0: nsresult rv = mDBFile->GetParent(getter_AddRefs(oldDBFile)); michael@0: RETURN_IF_FAILED(rv); michael@0: michael@0: rv = oldDBFile->AppendNative(NS_LITERAL_CSTRING("seer.sqlite")); michael@0: RETURN_IF_FAILED(rv); michael@0: michael@0: bool oldFileExists = false; michael@0: rv = oldDBFile->Exists(&oldFileExists); michael@0: if (NS_FAILED(rv) || !oldFileExists) { michael@0: return; michael@0: } michael@0: michael@0: oldDBFile->Remove(false); michael@0: } michael@0: michael@0: // Make sure that our sqlite storage is all set up with all the tables we need michael@0: // to do the work. It isn't the end of the world if this fails, since this is michael@0: // all an optimization, anyway. michael@0: michael@0: nsresult michael@0: Seer::EnsureInitStorage() michael@0: { michael@0: MOZ_ASSERT(!NS_IsMainThread(), "Initializing seer storage on main thread"); michael@0: michael@0: if (mDB) { michael@0: return NS_OK; michael@0: } michael@0: michael@0: nsresult rv; michael@0: michael@0: CheckForAndDeleteOldDBFile(); michael@0: michael@0: rv = mStorageService->OpenDatabase(mDBFile, getter_AddRefs(mDB)); michael@0: if (NS_FAILED(rv)) { michael@0: // Retry once by trashing the file and trying to open again. If this fails, michael@0: // we can just bail, and hope for better luck next time. michael@0: rv = mDBFile->Remove(false); michael@0: NS_ENSURE_SUCCESS(rv, rv); michael@0: michael@0: rv = mStorageService->OpenDatabase(mDBFile, getter_AddRefs(mDB)); michael@0: NS_ENSURE_SUCCESS(rv, rv); michael@0: } michael@0: michael@0: mDB->ExecuteSimpleSQL(NS_LITERAL_CSTRING("PRAGMA synchronous = OFF;")); michael@0: mDB->ExecuteSimpleSQL(NS_LITERAL_CSTRING("PRAGMA foreign_keys = ON;")); michael@0: michael@0: BeginTransaction(); michael@0: michael@0: // A table to make sure we're working with the database layout we expect michael@0: rv = mDB->ExecuteSimpleSQL( michael@0: NS_LITERAL_CSTRING("CREATE TABLE IF NOT EXISTS moz_seer_version (\n" michael@0: " version INTEGER NOT NULL\n" michael@0: ");\n")); michael@0: NS_ENSURE_SUCCESS(rv, rv); michael@0: michael@0: nsCOMPtr stmt; michael@0: rv = mDB->CreateStatement( michael@0: NS_LITERAL_CSTRING("SELECT version FROM moz_seer_version;\n"), michael@0: getter_AddRefs(stmt)); michael@0: NS_ENSURE_SUCCESS(rv, rv); michael@0: michael@0: bool hasRows; michael@0: rv = stmt->ExecuteStep(&hasRows); michael@0: NS_ENSURE_SUCCESS(rv, rv); michael@0: if (hasRows) { michael@0: int32_t currentVersion; michael@0: rv = stmt->GetInt32(0, ¤tVersion); michael@0: NS_ENSURE_SUCCESS(rv, rv); michael@0: michael@0: // This is what we do while we only have one schema version. Later, we'll michael@0: // have to change this to actually upgrade things as appropriate. michael@0: MOZ_ASSERT(currentVersion == SEER_SCHEMA_VERSION, michael@0: "Invalid seer schema version!"); michael@0: if (currentVersion != SEER_SCHEMA_VERSION) { michael@0: return NS_ERROR_UNEXPECTED; michael@0: } michael@0: } else { michael@0: stmt = nullptr; michael@0: rv = mDB->CreateStatement( michael@0: NS_LITERAL_CSTRING("INSERT INTO moz_seer_version (version) VALUES " michael@0: "(:seer_version);"), michael@0: getter_AddRefs(stmt)); michael@0: NS_ENSURE_SUCCESS(rv, rv); michael@0: michael@0: rv = stmt->BindInt32ByName(NS_LITERAL_CSTRING("seer_version"), michael@0: SEER_SCHEMA_VERSION); michael@0: NS_ENSURE_SUCCESS(rv, rv); michael@0: michael@0: stmt->Execute(); michael@0: } michael@0: michael@0: stmt = nullptr; michael@0: michael@0: // This table keeps track of the hosts we've seen at the top level of a michael@0: // pageload so we can map them to hosts used for subresources of a pageload. michael@0: rv = mDB->ExecuteSimpleSQL( michael@0: NS_LITERAL_CSTRING("CREATE TABLE IF NOT EXISTS moz_hosts (\n" michael@0: " id INTEGER PRIMARY KEY AUTOINCREMENT,\n" michael@0: " origin TEXT NOT NULL,\n" michael@0: " loads INTEGER DEFAULT 0,\n" michael@0: " last_load INTEGER DEFAULT 0\n" michael@0: ");\n")); michael@0: NS_ENSURE_SUCCESS(rv, rv); michael@0: michael@0: rv = mDB->ExecuteSimpleSQL( michael@0: NS_LITERAL_CSTRING("CREATE INDEX IF NOT EXISTS host_id_origin_index " michael@0: "ON moz_hosts (id, origin);")); michael@0: NS_ENSURE_SUCCESS(rv, rv); michael@0: michael@0: rv = mDB->ExecuteSimpleSQL( michael@0: NS_LITERAL_CSTRING("CREATE INDEX IF NOT EXISTS host_origin_index " michael@0: "ON moz_hosts (origin);")); michael@0: NS_ENSURE_SUCCESS(rv, rv); michael@0: michael@0: rv = mDB->ExecuteSimpleSQL( michael@0: NS_LITERAL_CSTRING("CREATE INDEX IF NOT EXISTS host_load_index " michael@0: "ON moz_hosts (last_load);")); michael@0: NS_ENSURE_SUCCESS(rv, rv); michael@0: michael@0: // And this is the table that keeps track of the hosts for subresources of a michael@0: // pageload. michael@0: rv = mDB->ExecuteSimpleSQL( michael@0: NS_LITERAL_CSTRING("CREATE TABLE IF NOT EXISTS moz_subhosts (\n" michael@0: " id INTEGER PRIMARY KEY AUTOINCREMENT,\n" michael@0: " hid INTEGER NOT NULL,\n" michael@0: " origin TEXT NOT NULL,\n" michael@0: " hits INTEGER DEFAULT 0,\n" michael@0: " last_hit INTEGER DEFAULT 0,\n" michael@0: " FOREIGN KEY(hid) REFERENCES moz_hosts(id) ON DELETE CASCADE\n" michael@0: ");\n")); michael@0: NS_ENSURE_SUCCESS(rv, rv); michael@0: michael@0: rv = mDB->ExecuteSimpleSQL( michael@0: NS_LITERAL_CSTRING("CREATE INDEX IF NOT EXISTS subhost_hid_origin_index " michael@0: "ON moz_subhosts (hid, origin);")); michael@0: NS_ENSURE_SUCCESS(rv, rv); michael@0: michael@0: rv = mDB->ExecuteSimpleSQL( michael@0: NS_LITERAL_CSTRING("CREATE INDEX IF NOT EXISTS subhost_id_index " michael@0: "ON moz_subhosts (id);")); michael@0: NS_ENSURE_SUCCESS(rv, rv); michael@0: michael@0: // Table to keep track of how many times we've started up, and when the last michael@0: // time was. michael@0: rv = mDB->ExecuteSimpleSQL( michael@0: NS_LITERAL_CSTRING("CREATE TABLE IF NOT EXISTS moz_startups (\n" michael@0: " startups INTEGER,\n" michael@0: " last_startup INTEGER\n" michael@0: ");\n")); michael@0: NS_ENSURE_SUCCESS(rv, rv); michael@0: michael@0: rv = mDB->CreateStatement( michael@0: NS_LITERAL_CSTRING("SELECT startups, last_startup FROM moz_startups;\n"), michael@0: getter_AddRefs(stmt)); michael@0: NS_ENSURE_SUCCESS(rv, rv); michael@0: michael@0: // We'll go ahead and keep track of our startup count here, since we can michael@0: // (mostly) equate "the service was created and asked to do stuff" with michael@0: // "the browser was started up". michael@0: rv = stmt->ExecuteStep(&hasRows); michael@0: NS_ENSURE_SUCCESS(rv, rv); michael@0: if (hasRows) { michael@0: // We've started up before. Update our startup statistics michael@0: stmt->GetInt32(0, &mStartupCount); michael@0: stmt->GetInt64(1, &mLastStartupTime); michael@0: michael@0: // This finalizes the statement michael@0: stmt = nullptr; michael@0: michael@0: rv = mDB->CreateStatement( michael@0: NS_LITERAL_CSTRING("UPDATE moz_startups SET startups = :startup_count, " michael@0: "last_startup = :startup_time;\n"), michael@0: getter_AddRefs(stmt)); michael@0: NS_ENSURE_SUCCESS(rv, rv); michael@0: michael@0: int32_t newStartupCount = mStartupCount + 1; michael@0: if (newStartupCount <= 0) { michael@0: SEER_LOG(("Seer::EnsureInitStorage startup count overflow\n")); michael@0: newStartupCount = mStartupCount; michael@0: ++mAccumulators->mStartupCountOverflows; michael@0: } michael@0: michael@0: rv = stmt->BindInt32ByName(NS_LITERAL_CSTRING("startup_count"), michael@0: mStartupCount + 1); michael@0: NS_ENSURE_SUCCESS(rv, rv); michael@0: michael@0: rv = stmt->BindInt64ByName(NS_LITERAL_CSTRING("startup_time"), michael@0: mStartupTime); michael@0: NS_ENSURE_SUCCESS(rv, rv); michael@0: michael@0: stmt->Execute(); michael@0: } else { michael@0: // This is our first startup, so let's go ahead and mark it as such michael@0: mStartupCount = 1; michael@0: michael@0: rv = mDB->CreateStatement( michael@0: NS_LITERAL_CSTRING("INSERT INTO moz_startups (startups, last_startup) " michael@0: "VALUES (1, :startup_time);\n"), michael@0: getter_AddRefs(stmt)); michael@0: NS_ENSURE_SUCCESS(rv, rv); michael@0: michael@0: rv = stmt->BindInt64ByName(NS_LITERAL_CSTRING("startup_time"), michael@0: mStartupTime); michael@0: NS_ENSURE_SUCCESS(rv, rv); michael@0: michael@0: stmt->Execute(); michael@0: } michael@0: michael@0: // This finalizes the statement michael@0: stmt = nullptr; michael@0: michael@0: // This table lists URIs loaded at startup, along with how many startups michael@0: // they've been loaded during, and when the last time was. michael@0: rv = mDB->ExecuteSimpleSQL( michael@0: NS_LITERAL_CSTRING("CREATE TABLE IF NOT EXISTS moz_startup_pages (\n" michael@0: " id INTEGER PRIMARY KEY AUTOINCREMENT,\n" michael@0: " uri TEXT NOT NULL,\n" michael@0: " hits INTEGER DEFAULT 0,\n" michael@0: " last_hit INTEGER DEFAULT 0\n" michael@0: ");\n")); michael@0: NS_ENSURE_SUCCESS(rv, rv); michael@0: michael@0: rv = mDB->ExecuteSimpleSQL( michael@0: NS_LITERAL_CSTRING("CREATE INDEX IF NOT EXISTS startup_page_uri_index " michael@0: "ON moz_startup_pages (uri);")); michael@0: NS_ENSURE_SUCCESS(rv, rv); michael@0: michael@0: rv = mDB->ExecuteSimpleSQL( michael@0: NS_LITERAL_CSTRING("CREATE INDEX IF NOT EXISTS startup_page_hit_index " michael@0: "ON moz_startup_pages (last_hit);")); michael@0: NS_ENSURE_SUCCESS(rv, rv); michael@0: michael@0: // This table is similar to moz_hosts above, but uses full URIs instead of michael@0: // hosts so that we can get more specific predictions for URIs that people michael@0: // visit often (such as their email or social network home pages). michael@0: rv = mDB->ExecuteSimpleSQL( michael@0: NS_LITERAL_CSTRING("CREATE TABLE IF NOT EXISTS moz_pages (\n" michael@0: " id integer PRIMARY KEY AUTOINCREMENT,\n" michael@0: " uri TEXT NOT NULL,\n" michael@0: " loads INTEGER DEFAULT 0,\n" michael@0: " last_load INTEGER DEFAULT 0\n" michael@0: ");\n")); michael@0: NS_ENSURE_SUCCESS(rv, rv); michael@0: michael@0: rv = mDB->ExecuteSimpleSQL( michael@0: NS_LITERAL_CSTRING("CREATE INDEX IF NOT EXISTS page_id_uri_index " michael@0: "ON moz_pages (id, uri);")); michael@0: NS_ENSURE_SUCCESS(rv, rv); michael@0: michael@0: rv = mDB->ExecuteSimpleSQL( michael@0: NS_LITERAL_CSTRING("CREATE INDEX IF NOT EXISTS page_uri_index " michael@0: "ON moz_pages (uri);")); michael@0: NS_ENSURE_SUCCESS(rv, rv); michael@0: michael@0: // This table is similar to moz_subhosts above, but is instead related to michael@0: // moz_pages for finer granularity. michael@0: rv = mDB->ExecuteSimpleSQL( michael@0: NS_LITERAL_CSTRING("CREATE TABLE IF NOT EXISTS moz_subresources (\n" michael@0: " id integer PRIMARY KEY AUTOINCREMENT,\n" michael@0: " pid INTEGER NOT NULL,\n" michael@0: " uri TEXT NOT NULL,\n" michael@0: " hits INTEGER DEFAULT 0,\n" michael@0: " last_hit INTEGER DEFAULT 0,\n" michael@0: " FOREIGN KEY(pid) REFERENCES moz_pages(id) ON DELETE CASCADE\n" michael@0: ");\n")); michael@0: NS_ENSURE_SUCCESS(rv, rv); michael@0: michael@0: rv = mDB->ExecuteSimpleSQL( michael@0: NS_LITERAL_CSTRING("CREATE INDEX IF NOT EXISTS subresource_pid_uri_index " michael@0: "ON moz_subresources (pid, uri);")); michael@0: NS_ENSURE_SUCCESS(rv, rv); michael@0: michael@0: rv = mDB->ExecuteSimpleSQL( michael@0: NS_LITERAL_CSTRING("CREATE INDEX IF NOT EXISTS subresource_id_index " michael@0: "ON moz_subresources (id);")); michael@0: NS_ENSURE_SUCCESS(rv, rv); michael@0: michael@0: // This table keeps track of URIs and what they end up finally redirecting to michael@0: // so we can handle redirects in a sane fashion, as well. michael@0: rv = mDB->ExecuteSimpleSQL( michael@0: NS_LITERAL_CSTRING("CREATE TABLE IF NOT EXISTS moz_redirects (\n" michael@0: " id integer PRIMARY KEY AUTOINCREMENT,\n" michael@0: " pid integer NOT NULL,\n" michael@0: " uri TEXT NOT NULL,\n" michael@0: " origin TEXT NOT NULL,\n" michael@0: " hits INTEGER DEFAULT 0,\n" michael@0: " last_hit INTEGER DEFAULT 0,\n" michael@0: " FOREIGN KEY(pid) REFERENCES moz_pages(id) ON DELETE CASCADE\n" michael@0: ");\n")); michael@0: NS_ENSURE_SUCCESS(rv, rv); michael@0: michael@0: rv = mDB->ExecuteSimpleSQL( michael@0: NS_LITERAL_CSTRING("CREATE INDEX IF NOT EXISTS redirect_pid_uri_index " michael@0: "ON moz_redirects (pid, uri);")); michael@0: NS_ENSURE_SUCCESS(rv, rv); michael@0: michael@0: rv = mDB->ExecuteSimpleSQL( michael@0: NS_LITERAL_CSTRING("CREATE INDEX IF NOT EXISTS redirect_id_index " michael@0: "ON moz_redirects (id);")); michael@0: NS_ENSURE_SUCCESS(rv, rv); michael@0: michael@0: CommitTransaction(); michael@0: BeginTransaction(); michael@0: michael@0: nsRefPtr event = new SeerCommitTimerInitEvent(); michael@0: NS_DispatchToMainThread(event, NS_DISPATCH_NORMAL); michael@0: michael@0: return NS_OK; michael@0: } michael@0: michael@0: class SeerThreadShutdownRunner : public nsRunnable michael@0: { michael@0: public: michael@0: SeerThreadShutdownRunner(nsIThread *ioThread) michael@0: :mIOThread(ioThread) michael@0: { } michael@0: michael@0: NS_IMETHODIMP Run() MOZ_OVERRIDE michael@0: { michael@0: MOZ_ASSERT(NS_IsMainThread(), "Shut down seer io thread off main thread"); michael@0: mIOThread->Shutdown(); michael@0: return NS_OK; michael@0: } michael@0: michael@0: private: michael@0: nsCOMPtr mIOThread; michael@0: }; michael@0: michael@0: class SeerDBShutdownRunner : public nsRunnable michael@0: { michael@0: public: michael@0: SeerDBShutdownRunner(nsIThread *ioThread, nsINetworkSeer *seer) michael@0: :mIOThread(ioThread) michael@0: { michael@0: mSeer = new nsMainThreadPtrHolder(seer); michael@0: } michael@0: michael@0: NS_IMETHODIMP Run() MOZ_OVERRIDE michael@0: { michael@0: MOZ_ASSERT(!NS_IsMainThread(), "Shutting down DB on main thread"); michael@0: michael@0: // Ensure everything is written to disk before we shut down the db michael@0: gSeer->CommitTransaction(); michael@0: michael@0: gSeer->mStatements.FinalizeStatements(); michael@0: gSeer->mDB->Close(); michael@0: gSeer->mDB = nullptr; michael@0: michael@0: nsRefPtr runner = michael@0: new SeerThreadShutdownRunner(mIOThread); michael@0: NS_DispatchToMainThread(runner); michael@0: michael@0: return NS_OK; michael@0: } michael@0: michael@0: private: michael@0: nsCOMPtr mIOThread; michael@0: michael@0: // Death grip to keep seer alive while we cleanly close its DB connection michael@0: nsMainThreadPtrHandle mSeer; michael@0: }; michael@0: michael@0: void michael@0: Seer::Shutdown() michael@0: { michael@0: if (!NS_IsMainThread()) { michael@0: MOZ_ASSERT(false, "Seer::Shutdown called off the main thread!"); michael@0: return; michael@0: } michael@0: michael@0: mInitialized = false; michael@0: michael@0: if (mCommitTimer) { michael@0: mCommitTimer->Cancel(); michael@0: } michael@0: michael@0: if (mIOThread) { michael@0: if (mDB) { michael@0: nsRefPtr runner = michael@0: new SeerDBShutdownRunner(mIOThread, this); michael@0: mIOThread->Dispatch(runner, NS_DISPATCH_NORMAL); michael@0: } else { michael@0: nsRefPtr runner = michael@0: new SeerThreadShutdownRunner(mIOThread); michael@0: NS_DispatchToMainThread(runner); michael@0: } michael@0: } michael@0: } michael@0: michael@0: nsresult michael@0: Seer::Create(nsISupports *aOuter, const nsIID& aIID, michael@0: void **aResult) michael@0: { michael@0: nsresult rv; michael@0: michael@0: if (aOuter != nullptr) { michael@0: return NS_ERROR_NO_AGGREGATION; michael@0: } michael@0: michael@0: nsRefPtr svc = new Seer(); michael@0: michael@0: rv = svc->Init(); michael@0: if (NS_FAILED(rv)) { michael@0: SEER_LOG(("Failed to initialize seer, seer will be a noop")); michael@0: } michael@0: michael@0: // We treat init failure the same as the service being disabled, since this michael@0: // is all an optimization anyway. No need to freak people out. That's why we michael@0: // gladly continue on QI'ing here. michael@0: rv = svc->QueryInterface(aIID, aResult); michael@0: michael@0: return rv; michael@0: } michael@0: michael@0: // Get the full origin (scheme, host, port) out of a URI (maybe should be part michael@0: // of nsIURI instead?) michael@0: static void michael@0: ExtractOrigin(nsIURI *uri, nsAutoCString &s) michael@0: { michael@0: s.Truncate(); michael@0: michael@0: nsAutoCString scheme; michael@0: nsresult rv = uri->GetScheme(scheme); michael@0: RETURN_IF_FAILED(rv); michael@0: michael@0: nsAutoCString host; michael@0: rv = uri->GetAsciiHost(host); michael@0: RETURN_IF_FAILED(rv); michael@0: michael@0: int32_t port; michael@0: rv = uri->GetPort(&port); michael@0: RETURN_IF_FAILED(rv); michael@0: michael@0: s.Assign(scheme); michael@0: s.AppendLiteral("://"); michael@0: s.Append(host); michael@0: if (port != -1) { michael@0: s.AppendLiteral(":"); michael@0: s.AppendInt(port); michael@0: } michael@0: } michael@0: michael@0: // An event to do the work for a prediction that needs to hit the sqlite michael@0: // database. These events should be created on the main thread, and run on michael@0: // the seer thread. michael@0: class SeerPredictionEvent : public nsRunnable michael@0: { michael@0: public: michael@0: SeerPredictionEvent(nsIURI *targetURI, nsIURI *sourceURI, michael@0: SeerPredictReason reason, michael@0: nsINetworkSeerVerifier *verifier) michael@0: :mReason(reason) michael@0: { michael@0: MOZ_ASSERT(NS_IsMainThread(), "Creating prediction event off main thread"); michael@0: michael@0: mEnqueueTime = TimeStamp::Now(); michael@0: michael@0: if (verifier) { michael@0: mVerifier = new nsMainThreadPtrHolder(verifier); michael@0: } michael@0: if (targetURI) { michael@0: targetURI->GetAsciiSpec(mTargetURI.spec); michael@0: ExtractOrigin(targetURI, mTargetURI.origin); michael@0: } michael@0: if (sourceURI) { michael@0: sourceURI->GetAsciiSpec(mSourceURI.spec); michael@0: ExtractOrigin(sourceURI, mSourceURI.origin); michael@0: } michael@0: } michael@0: michael@0: NS_IMETHOD Run() MOZ_OVERRIDE michael@0: { michael@0: MOZ_ASSERT(!NS_IsMainThread(), "Running prediction event on main thread"); michael@0: michael@0: Telemetry::AccumulateTimeDelta(Telemetry::SEER_PREDICT_WAIT_TIME, michael@0: mEnqueueTime); michael@0: michael@0: TimeStamp startTime = TimeStamp::Now(); michael@0: michael@0: nsresult rv = NS_OK; michael@0: michael@0: switch (mReason) { michael@0: case nsINetworkSeer::PREDICT_LOAD: michael@0: gSeer->PredictForPageload(mTargetURI, mVerifier, 0, mEnqueueTime); michael@0: break; michael@0: case nsINetworkSeer::PREDICT_STARTUP: michael@0: gSeer->PredictForStartup(mVerifier, mEnqueueTime); michael@0: break; michael@0: default: michael@0: MOZ_ASSERT(false, "Got unexpected value for predict reason"); michael@0: rv = NS_ERROR_UNEXPECTED; michael@0: } michael@0: michael@0: gSeer->FreeSpaceInQueue(); michael@0: michael@0: Telemetry::AccumulateTimeDelta(Telemetry::SEER_PREDICT_WORK_TIME, michael@0: startTime); michael@0: michael@0: gSeer->MaybeScheduleCleanup(); michael@0: michael@0: return rv; michael@0: } michael@0: michael@0: private: michael@0: Seer::UriInfo mTargetURI; michael@0: Seer::UriInfo mSourceURI; michael@0: SeerPredictReason mReason; michael@0: SeerVerifierHandle mVerifier; michael@0: TimeStamp mEnqueueTime; michael@0: }; michael@0: michael@0: // Predicting for a link is easy, and doesn't require the round-trip to the michael@0: // seer thread and back to the main thread, since we don't have to hit the db michael@0: // for that. michael@0: void michael@0: Seer::PredictForLink(nsIURI *targetURI, nsIURI *sourceURI, michael@0: nsINetworkSeerVerifier *verifier) michael@0: { michael@0: MOZ_ASSERT(NS_IsMainThread(), "Predicting for link off main thread"); michael@0: michael@0: if (!mSpeculativeService) { michael@0: return; michael@0: } michael@0: michael@0: if (!mEnableHoverOnSSL) { michael@0: bool isSSL = false; michael@0: sourceURI->SchemeIs("https", &isSSL); michael@0: if (isSSL) { michael@0: // We don't want to predict from an HTTPS page, to avoid info leakage michael@0: SEER_LOG(("Not predicting for link hover - on an SSL page")); michael@0: return; michael@0: } michael@0: } michael@0: michael@0: mSpeculativeService->SpeculativeConnect(targetURI, nullptr); michael@0: if (verifier) { michael@0: verifier->OnPredictPreconnect(targetURI); michael@0: } michael@0: } michael@0: michael@0: // This runnable runs on the main thread, and is responsible for actually michael@0: // firing off predictive actions (such as TCP/TLS preconnects and DNS lookups) michael@0: class SeerPredictionRunner : public nsRunnable michael@0: { michael@0: public: michael@0: SeerPredictionRunner(SeerVerifierHandle &verifier, TimeStamp predictStartTime) michael@0: :mVerifier(verifier) michael@0: ,mPredictStartTime(predictStartTime) michael@0: { } michael@0: michael@0: void AddPreconnect(const nsACString &uri) michael@0: { michael@0: mPreconnects.AppendElement(uri); michael@0: } michael@0: michael@0: void AddPreresolve(const nsACString &uri) michael@0: { michael@0: mPreresolves.AppendElement(uri); michael@0: } michael@0: michael@0: bool HasWork() const michael@0: { michael@0: return !(mPreconnects.IsEmpty() && mPreresolves.IsEmpty()); michael@0: } michael@0: michael@0: NS_IMETHOD Run() MOZ_OVERRIDE michael@0: { michael@0: MOZ_ASSERT(NS_IsMainThread(), "Running prediction off main thread"); michael@0: michael@0: Telemetry::AccumulateTimeDelta(Telemetry::SEER_PREDICT_TIME_TO_ACTION, michael@0: mPredictStartTime); michael@0: michael@0: uint32_t len, i; michael@0: michael@0: len = mPreconnects.Length(); michael@0: for (i = 0; i < len; ++i) { michael@0: nsCOMPtr uri; michael@0: nsresult rv = NS_NewURI(getter_AddRefs(uri), mPreconnects[i]); michael@0: if (NS_FAILED(rv)) { michael@0: continue; michael@0: } michael@0: michael@0: ++gSeer->mAccumulators->mTotalPredictions; michael@0: ++gSeer->mAccumulators->mTotalPreconnects; michael@0: gSeer->mSpeculativeService->SpeculativeConnect(uri, gSeer); michael@0: if (mVerifier) { michael@0: mVerifier->OnPredictPreconnect(uri); michael@0: } michael@0: } michael@0: michael@0: len = mPreresolves.Length(); michael@0: nsCOMPtr mainThread = do_GetMainThread(); michael@0: for (i = 0; i < len; ++i) { michael@0: nsCOMPtr uri; michael@0: nsresult rv = NS_NewURI(getter_AddRefs(uri), mPreresolves[i]); michael@0: if (NS_FAILED(rv)) { michael@0: continue; michael@0: } michael@0: michael@0: ++gSeer->mAccumulators->mTotalPredictions; michael@0: ++gSeer->mAccumulators->mTotalPreresolves; michael@0: nsAutoCString hostname; michael@0: uri->GetAsciiHost(hostname); michael@0: nsCOMPtr tmpCancelable; michael@0: gSeer->mDnsService->AsyncResolve(hostname, michael@0: (nsIDNSService::RESOLVE_PRIORITY_MEDIUM | michael@0: nsIDNSService::RESOLVE_SPECULATE), michael@0: gSeer->mDNSListener, nullptr, michael@0: getter_AddRefs(tmpCancelable)); michael@0: if (mVerifier) { michael@0: mVerifier->OnPredictDNS(uri); michael@0: } michael@0: } michael@0: michael@0: mPreconnects.Clear(); michael@0: mPreresolves.Clear(); michael@0: michael@0: return NS_OK; michael@0: } michael@0: michael@0: private: michael@0: nsTArray mPreconnects; michael@0: nsTArray mPreresolves; michael@0: SeerVerifierHandle mVerifier; michael@0: TimeStamp mPredictStartTime; michael@0: }; michael@0: michael@0: // This calculates how much to degrade our confidence in our data based on michael@0: // the last time this top-level resource was loaded. This "global degradation" michael@0: // applies to *all* subresources we have associated with the top-level michael@0: // resource. This will be in addition to any reduction in confidence we have michael@0: // associated with a particular subresource. michael@0: int michael@0: Seer::CalculateGlobalDegradation(PRTime now, PRTime lastLoad) michael@0: { michael@0: int globalDegradation; michael@0: PRTime delta = now - lastLoad; michael@0: if (delta < ONE_DAY) { michael@0: globalDegradation = mPageDegradationDay; michael@0: } else if (delta < ONE_WEEK) { michael@0: globalDegradation = mPageDegradationWeek; michael@0: } else if (delta < ONE_MONTH) { michael@0: globalDegradation = mPageDegradationMonth; michael@0: } else if (delta < ONE_YEAR) { michael@0: globalDegradation = mPageDegradationYear; michael@0: } else { michael@0: globalDegradation = mPageDegradationMax; michael@0: } michael@0: michael@0: Telemetry::Accumulate(Telemetry::SEER_GLOBAL_DEGRADATION, globalDegradation); michael@0: return globalDegradation; michael@0: } michael@0: michael@0: // This calculates our overall confidence that a particular subresource will be michael@0: // loaded as part of a top-level load. michael@0: // @param baseConfidence - the basic confidence we have for this subresource, michael@0: // which is the percentage of time this top-level load michael@0: // loads the subresource in question michael@0: // @param lastHit - the timestamp of the last time we loaded this subresource as michael@0: // part of this top-level load michael@0: // @param lastPossible - the timestamp of the last time we performed this michael@0: // top-level load michael@0: // @param globalDegradation - the degradation for this top-level load as michael@0: // determined by CalculateGlobalDegradation michael@0: int michael@0: Seer::CalculateConfidence(int baseConfidence, PRTime lastHit, michael@0: PRTime lastPossible, int globalDegradation) michael@0: { michael@0: ++mAccumulators->mPredictionsCalculated; michael@0: michael@0: int maxConfidence = 100; michael@0: int confidenceDegradation = 0; michael@0: michael@0: if (lastHit < lastPossible) { michael@0: // We didn't load this subresource the last time this top-level load was michael@0: // performed, so let's not bother preconnecting (at the very least). michael@0: maxConfidence = mPreconnectMinConfidence - 1; michael@0: michael@0: // Now calculate how much we want to degrade our confidence based on how michael@0: // long it's been between the last time we did this top-level load and the michael@0: // last time this top-level load included this subresource. michael@0: PRTime delta = lastPossible - lastHit; michael@0: if (delta == 0) { michael@0: confidenceDegradation = 0; michael@0: } else if (delta < ONE_DAY) { michael@0: confidenceDegradation = mSubresourceDegradationDay; michael@0: } else if (delta < ONE_WEEK) { michael@0: confidenceDegradation = mSubresourceDegradationWeek; michael@0: } else if (delta < ONE_MONTH) { michael@0: confidenceDegradation = mSubresourceDegradationMonth; michael@0: } else if (delta < ONE_YEAR) { michael@0: confidenceDegradation = mSubresourceDegradationYear; michael@0: } else { michael@0: confidenceDegradation = mSubresourceDegradationMax; michael@0: maxConfidence = 0; michael@0: } michael@0: } michael@0: michael@0: // Calculate our confidence and clamp it to between 0 and maxConfidence michael@0: // (<= 100) michael@0: int confidence = baseConfidence - confidenceDegradation - globalDegradation; michael@0: confidence = std::max(confidence, 0); michael@0: confidence = std::min(confidence, maxConfidence); michael@0: michael@0: Telemetry::Accumulate(Telemetry::SEER_BASE_CONFIDENCE, baseConfidence); michael@0: Telemetry::Accumulate(Telemetry::SEER_SUBRESOURCE_DEGRADATION, michael@0: confidenceDegradation); michael@0: Telemetry::Accumulate(Telemetry::SEER_CONFIDENCE, confidence); michael@0: return confidence; michael@0: } michael@0: michael@0: // (Maybe) adds a predictive action to the prediction runner, based on our michael@0: // calculated confidence for the subresource in question. michael@0: void michael@0: Seer::SetupPrediction(int confidence, const nsACString &uri, michael@0: SeerPredictionRunner *runner) michael@0: { michael@0: if (confidence >= mPreconnectMinConfidence) { michael@0: runner->AddPreconnect(uri); michael@0: } else if (confidence >= mPreresolveMinConfidence) { michael@0: runner->AddPreresolve(uri); michael@0: } michael@0: } michael@0: michael@0: // This gets the data about the top-level load from our database, either from michael@0: // the pages table (which is specific to a particular URI), or from the hosts michael@0: // table (which is for a particular origin). michael@0: bool michael@0: Seer::LookupTopLevel(QueryType queryType, const nsACString &key, michael@0: TopLevelInfo &info) michael@0: { michael@0: MOZ_ASSERT(!NS_IsMainThread(), "LookupTopLevel called on main thread."); michael@0: michael@0: nsCOMPtr stmt; michael@0: if (queryType == QUERY_PAGE) { michael@0: stmt = mStatements.GetCachedStatement( michael@0: NS_LITERAL_CSTRING("SELECT id, loads, last_load FROM moz_pages WHERE " michael@0: "uri = :key;")); michael@0: } else { michael@0: stmt = mStatements.GetCachedStatement( michael@0: NS_LITERAL_CSTRING("SELECT id, loads, last_load FROM moz_hosts WHERE " michael@0: "origin = :key;")); michael@0: } michael@0: NS_ENSURE_TRUE(stmt, false); michael@0: mozStorageStatementScoper scope(stmt); michael@0: michael@0: nsresult rv = stmt->BindUTF8StringByName(NS_LITERAL_CSTRING("key"), key); michael@0: NS_ENSURE_SUCCESS(rv, false); michael@0: michael@0: bool hasRows; michael@0: rv = stmt->ExecuteStep(&hasRows); michael@0: NS_ENSURE_SUCCESS(rv, false); michael@0: michael@0: if (!hasRows) { michael@0: return false; michael@0: } michael@0: michael@0: rv = stmt->GetInt32(0, &info.id); michael@0: NS_ENSURE_SUCCESS(rv, false); michael@0: michael@0: rv = stmt->GetInt32(1, &info.loadCount); michael@0: NS_ENSURE_SUCCESS(rv, false); michael@0: michael@0: rv = stmt->GetInt64(2, &info.lastLoad); michael@0: NS_ENSURE_SUCCESS(rv, false); michael@0: michael@0: return true; michael@0: } michael@0: michael@0: // Insert data about either a top-level page or a top-level origin into michael@0: // the database. michael@0: void michael@0: Seer::AddTopLevel(QueryType queryType, const nsACString &key, PRTime now) michael@0: { michael@0: MOZ_ASSERT(!NS_IsMainThread(), "AddTopLevel called on main thread."); michael@0: michael@0: nsCOMPtr stmt; michael@0: if (queryType == QUERY_PAGE) { michael@0: stmt = mStatements.GetCachedStatement( michael@0: NS_LITERAL_CSTRING("INSERT INTO moz_pages (uri, loads, last_load) " michael@0: "VALUES (:key, 1, :now);")); michael@0: } else { michael@0: stmt = mStatements.GetCachedStatement( michael@0: NS_LITERAL_CSTRING("INSERT INTO moz_hosts (origin, loads, last_load) " michael@0: "VALUES (:key, 1, :now);")); michael@0: } michael@0: if (!stmt) { michael@0: return; michael@0: } michael@0: mozStorageStatementScoper scope(stmt); michael@0: michael@0: // Loading a page implicitly makes the seer learn about the page, michael@0: // so since we don't have it already, let's add it. michael@0: nsresult rv = stmt->BindUTF8StringByName(NS_LITERAL_CSTRING("key"), key); michael@0: RETURN_IF_FAILED(rv); michael@0: michael@0: rv = stmt->BindInt64ByName(NS_LITERAL_CSTRING("now"), now); michael@0: RETURN_IF_FAILED(rv); michael@0: michael@0: rv = stmt->Execute(); michael@0: } michael@0: michael@0: // Update data about either a top-level page or a top-level origin in the michael@0: // database. michael@0: void michael@0: Seer::UpdateTopLevel(QueryType queryType, const TopLevelInfo &info, PRTime now) michael@0: { michael@0: MOZ_ASSERT(!NS_IsMainThread(), "UpdateTopLevel called on main thread."); michael@0: michael@0: nsCOMPtr stmt; michael@0: if (queryType == QUERY_PAGE) { michael@0: stmt = mStatements.GetCachedStatement( michael@0: NS_LITERAL_CSTRING("UPDATE moz_pages SET loads = :load_count, " michael@0: "last_load = :now WHERE id = :id;")); michael@0: } else { michael@0: stmt = mStatements.GetCachedStatement( michael@0: NS_LITERAL_CSTRING("UPDATE moz_hosts SET loads = :load_count, " michael@0: "last_load = :now WHERE id = :id;")); michael@0: } michael@0: if (!stmt) { michael@0: return; michael@0: } michael@0: mozStorageStatementScoper scope(stmt); michael@0: michael@0: int32_t newLoadCount = info.loadCount + 1; michael@0: if (newLoadCount <= 0) { michael@0: SEER_LOG(("Seer::UpdateTopLevel type %d id %d load count overflow\n", michael@0: queryType, info.id)); michael@0: newLoadCount = info.loadCount; michael@0: ++mAccumulators->mLoadCountOverflows; michael@0: } michael@0: michael@0: // First, let's update the page in the database, since loading a page michael@0: // implicitly learns about the page. michael@0: nsresult rv = stmt->BindInt32ByName(NS_LITERAL_CSTRING("load_count"), michael@0: newLoadCount); michael@0: RETURN_IF_FAILED(rv); michael@0: michael@0: rv = stmt->BindInt64ByName(NS_LITERAL_CSTRING("now"), now); michael@0: RETURN_IF_FAILED(rv); michael@0: michael@0: rv = stmt->BindInt32ByName(NS_LITERAL_CSTRING("id"), info.id); michael@0: RETURN_IF_FAILED(rv); michael@0: michael@0: rv = stmt->Execute(); michael@0: } michael@0: michael@0: // Tries to predict for a top-level load (either page-based or origin-based). michael@0: // Returns false if it failed to predict at all, true if it did some sort of michael@0: // prediction. michael@0: // @param queryType - whether to predict based on page or origin michael@0: // @param info - the db info about the top-level resource michael@0: bool michael@0: Seer::TryPredict(QueryType queryType, const TopLevelInfo &info, PRTime now, michael@0: SeerVerifierHandle &verifier, TimeStamp &predictStartTime) michael@0: { michael@0: MOZ_ASSERT(!NS_IsMainThread(), "TryPredict called on main thread."); michael@0: michael@0: if (!info.loadCount) { michael@0: SEER_LOG(("Seer::TryPredict info.loadCount is zero!\n")); michael@0: ++mAccumulators->mLoadCountZeroes; michael@0: return false; michael@0: } michael@0: michael@0: int globalDegradation = CalculateGlobalDegradation(now, info.lastLoad); michael@0: michael@0: // Now let's look up the subresources we know about for this page michael@0: nsCOMPtr stmt; michael@0: if (queryType == QUERY_PAGE) { michael@0: stmt = mStatements.GetCachedStatement( michael@0: NS_LITERAL_CSTRING("SELECT uri, hits, last_hit FROM moz_subresources " michael@0: "WHERE pid = :id;")); michael@0: } else { michael@0: stmt = mStatements.GetCachedStatement( michael@0: NS_LITERAL_CSTRING("SELECT origin, hits, last_hit FROM moz_subhosts " michael@0: "WHERE hid = :id;")); michael@0: } michael@0: NS_ENSURE_TRUE(stmt, false); michael@0: mozStorageStatementScoper scope(stmt); michael@0: michael@0: nsresult rv = stmt->BindInt32ByName(NS_LITERAL_CSTRING("id"), info.id); michael@0: NS_ENSURE_SUCCESS(rv, false); michael@0: michael@0: bool hasRows; michael@0: rv = stmt->ExecuteStep(&hasRows); michael@0: if (NS_FAILED(rv) || !hasRows) { michael@0: return false; michael@0: } michael@0: michael@0: nsRefPtr runner = michael@0: new SeerPredictionRunner(verifier, predictStartTime); michael@0: michael@0: while (hasRows) { michael@0: int32_t hitCount; michael@0: PRTime lastHit; michael@0: nsAutoCString subresource; michael@0: int baseConfidence, confidence; michael@0: michael@0: // We use goto nextrow here instead of just failling, because we want michael@0: // to do some sort of prediction if at all possible. Of course, it's michael@0: // probably unlikely that subsequent rows will succeed if one fails, but michael@0: // it's worth a shot. michael@0: michael@0: rv = stmt->GetUTF8String(0, subresource); michael@0: if NS_FAILED(rv) { michael@0: goto nextrow; michael@0: } michael@0: michael@0: rv = stmt->GetInt32(1, &hitCount); michael@0: if (NS_FAILED(rv)) { michael@0: goto nextrow; michael@0: } michael@0: michael@0: rv = stmt->GetInt64(2, &lastHit); michael@0: if (NS_FAILED(rv)) { michael@0: goto nextrow; michael@0: } michael@0: michael@0: baseConfidence = (hitCount * 100) / info.loadCount; michael@0: confidence = CalculateConfidence(baseConfidence, lastHit, info.lastLoad, michael@0: globalDegradation); michael@0: SetupPrediction(confidence, subresource, runner); michael@0: michael@0: nextrow: michael@0: rv = stmt->ExecuteStep(&hasRows); michael@0: NS_ENSURE_SUCCESS(rv, false); michael@0: } michael@0: michael@0: bool predicted = false; michael@0: michael@0: if (runner->HasWork()) { michael@0: NS_DispatchToMainThread(runner); michael@0: predicted = true; michael@0: } michael@0: michael@0: return predicted; michael@0: } michael@0: michael@0: // Find out if a top-level page is likely to redirect. michael@0: bool michael@0: Seer::WouldRedirect(const TopLevelInfo &info, PRTime now, UriInfo &newUri) michael@0: { michael@0: MOZ_ASSERT(!NS_IsMainThread(), "WouldRedirect called on main thread."); michael@0: michael@0: if (!info.loadCount) { michael@0: SEER_LOG(("Seer::WouldRedirect info.loadCount is zero!\n")); michael@0: ++mAccumulators->mLoadCountZeroes; michael@0: return false; michael@0: } michael@0: michael@0: nsCOMPtr stmt = mStatements.GetCachedStatement( michael@0: NS_LITERAL_CSTRING("SELECT uri, origin, hits, last_hit " michael@0: "FROM moz_redirects WHERE pid = :id;")); michael@0: NS_ENSURE_TRUE(stmt, false); michael@0: mozStorageStatementScoper scope(stmt); michael@0: michael@0: nsresult rv = stmt->BindInt32ByName(NS_LITERAL_CSTRING("id"), info.id); michael@0: NS_ENSURE_SUCCESS(rv, false); michael@0: michael@0: bool hasRows; michael@0: rv = stmt->ExecuteStep(&hasRows); michael@0: if (NS_FAILED(rv) || !hasRows) { michael@0: return false; michael@0: } michael@0: michael@0: rv = stmt->GetUTF8String(0, newUri.spec); michael@0: NS_ENSURE_SUCCESS(rv, false); michael@0: michael@0: rv = stmt->GetUTF8String(1, newUri.origin); michael@0: NS_ENSURE_SUCCESS(rv, false); michael@0: michael@0: int32_t hitCount; michael@0: rv = stmt->GetInt32(2, &hitCount); michael@0: NS_ENSURE_SUCCESS(rv, false); michael@0: michael@0: PRTime lastHit; michael@0: rv = stmt->GetInt64(3, &lastHit); michael@0: NS_ENSURE_SUCCESS(rv, false); michael@0: michael@0: int globalDegradation = CalculateGlobalDegradation(now, info.lastLoad); michael@0: int baseConfidence = (hitCount * 100) / info.loadCount; michael@0: int confidence = CalculateConfidence(baseConfidence, lastHit, info.lastLoad, michael@0: globalDegradation); michael@0: michael@0: if (confidence > mRedirectLikelyConfidence) { michael@0: return true; michael@0: } michael@0: michael@0: return false; michael@0: } michael@0: michael@0: // This will add a page to our list of startup pages if it's being loaded michael@0: // before our startup window has expired. michael@0: void michael@0: Seer::MaybeLearnForStartup(const UriInfo &uri, const PRTime now) michael@0: { michael@0: MOZ_ASSERT(!NS_IsMainThread(), "MaybeLearnForStartup called on main thread."); michael@0: michael@0: if ((now - mStartupTime) < STARTUP_WINDOW) { michael@0: LearnForStartup(uri); michael@0: } michael@0: } michael@0: michael@0: const int MAX_PAGELOAD_DEPTH = 10; michael@0: michael@0: // This is the driver for prediction based on a new pageload. michael@0: void michael@0: Seer::PredictForPageload(const UriInfo &uri, SeerVerifierHandle &verifier, michael@0: int stackCount, TimeStamp &predictStartTime) michael@0: { michael@0: MOZ_ASSERT(!NS_IsMainThread(), "PredictForPageload called on main thread."); michael@0: michael@0: if (stackCount > MAX_PAGELOAD_DEPTH) { michael@0: SEER_LOG(("Too deep into pageload prediction")); michael@0: return; michael@0: } michael@0: michael@0: if (NS_FAILED(EnsureInitStorage())) { michael@0: return; michael@0: } michael@0: michael@0: PRTime now = PR_Now(); michael@0: michael@0: MaybeLearnForStartup(uri, now); michael@0: michael@0: TopLevelInfo pageInfo; michael@0: TopLevelInfo originInfo; michael@0: bool havePage = LookupTopLevel(QUERY_PAGE, uri.spec, pageInfo); michael@0: bool haveOrigin = LookupTopLevel(QUERY_ORIGIN, uri.origin, originInfo); michael@0: michael@0: if (!havePage) { michael@0: AddTopLevel(QUERY_PAGE, uri.spec, now); michael@0: } else { michael@0: UpdateTopLevel(QUERY_PAGE, pageInfo, now); michael@0: } michael@0: michael@0: if (!haveOrigin) { michael@0: AddTopLevel(QUERY_ORIGIN, uri.origin, now); michael@0: } else { michael@0: UpdateTopLevel(QUERY_ORIGIN, originInfo, now); michael@0: } michael@0: michael@0: UriInfo newUri; michael@0: if (havePage && WouldRedirect(pageInfo, now, newUri)) { michael@0: nsRefPtr runner = michael@0: new SeerPredictionRunner(verifier, predictStartTime); michael@0: runner->AddPreconnect(newUri.spec); michael@0: NS_DispatchToMainThread(runner); michael@0: PredictForPageload(newUri, verifier, stackCount + 1, predictStartTime); michael@0: return; michael@0: } michael@0: michael@0: bool predicted = false; michael@0: michael@0: // We always try to be as specific as possible in our predictions, so try michael@0: // to predict based on the full URI before we fall back to the origin. michael@0: if (havePage) { michael@0: predicted = TryPredict(QUERY_PAGE, pageInfo, now, verifier, michael@0: predictStartTime); michael@0: } michael@0: michael@0: if (!predicted && haveOrigin) { michael@0: predicted = TryPredict(QUERY_ORIGIN, originInfo, now, verifier, michael@0: predictStartTime); michael@0: } michael@0: michael@0: if (!predicted) { michael@0: Telemetry::AccumulateTimeDelta(Telemetry::SEER_PREDICT_TIME_TO_INACTION, michael@0: predictStartTime); michael@0: } michael@0: } michael@0: michael@0: // This is the driver for predicting at browser startup time based on pages that michael@0: // have previously been loaded close to startup. michael@0: void michael@0: Seer::PredictForStartup(SeerVerifierHandle &verifier, michael@0: TimeStamp &predictStartTime) michael@0: { michael@0: MOZ_ASSERT(!NS_IsMainThread(), "PredictForStartup called on main thread"); michael@0: michael@0: if (!mStartupCount) { michael@0: SEER_LOG(("Seer::PredictForStartup mStartupCount is zero!\n")); michael@0: ++mAccumulators->mStartupCountZeroes; michael@0: return; michael@0: } michael@0: michael@0: if (NS_FAILED(EnsureInitStorage())) { michael@0: return; michael@0: } michael@0: michael@0: nsCOMPtr stmt = mStatements.GetCachedStatement( michael@0: NS_LITERAL_CSTRING("SELECT uri, hits, last_hit FROM moz_startup_pages;")); michael@0: if (!stmt) { michael@0: return; michael@0: } michael@0: mozStorageStatementScoper scope(stmt); michael@0: nsresult rv; michael@0: bool hasRows; michael@0: michael@0: nsRefPtr runner = michael@0: new SeerPredictionRunner(verifier, predictStartTime); michael@0: michael@0: rv = stmt->ExecuteStep(&hasRows); michael@0: RETURN_IF_FAILED(rv); michael@0: michael@0: while (hasRows) { michael@0: nsAutoCString uri; michael@0: int32_t hitCount; michael@0: PRTime lastHit; michael@0: int baseConfidence, confidence; michael@0: michael@0: // We use goto nextrow here instead of just failling, because we want michael@0: // to do some sort of prediction if at all possible. Of course, it's michael@0: // probably unlikely that subsequent rows will succeed if one fails, but michael@0: // it's worth a shot. michael@0: michael@0: rv = stmt->GetUTF8String(0, uri); michael@0: if (NS_FAILED(rv)) { michael@0: goto nextrow; michael@0: } michael@0: michael@0: rv = stmt->GetInt32(1, &hitCount); michael@0: if (NS_FAILED(rv)) { michael@0: goto nextrow; michael@0: } michael@0: michael@0: rv = stmt->GetInt64(2, &lastHit); michael@0: if (NS_FAILED(rv)) { michael@0: goto nextrow; michael@0: } michael@0: michael@0: baseConfidence = (hitCount * 100) / mStartupCount; michael@0: confidence = CalculateConfidence(baseConfidence, lastHit, michael@0: mLastStartupTime, 0); michael@0: SetupPrediction(confidence, uri, runner); michael@0: michael@0: nextrow: michael@0: rv = stmt->ExecuteStep(&hasRows); michael@0: RETURN_IF_FAILED(rv); michael@0: } michael@0: michael@0: if (runner->HasWork()) { michael@0: NS_DispatchToMainThread(runner); michael@0: } else { michael@0: Telemetry::AccumulateTimeDelta(Telemetry::SEER_PREDICT_TIME_TO_INACTION, michael@0: predictStartTime); michael@0: } michael@0: } michael@0: michael@0: // All URIs we get passed *must* be http or https if they're not null. This michael@0: // helps ensure that. michael@0: static bool michael@0: IsNullOrHttp(nsIURI *uri) michael@0: { michael@0: if (!uri) { michael@0: return true; michael@0: } michael@0: michael@0: bool isHTTP = false; michael@0: uri->SchemeIs("http", &isHTTP); michael@0: if (!isHTTP) { michael@0: uri->SchemeIs("https", &isHTTP); michael@0: } michael@0: michael@0: return isHTTP; michael@0: } michael@0: michael@0: nsresult michael@0: Seer::ReserveSpaceInQueue() michael@0: { michael@0: MutexAutoLock lock(mQueueSizeLock); michael@0: michael@0: if (mQueueSize >= mMaxQueueSize) { michael@0: SEER_LOG(("Not enqueuing event - queue too large")); michael@0: return NS_ERROR_NOT_AVAILABLE; michael@0: } michael@0: michael@0: mQueueSize++; michael@0: return NS_OK; michael@0: } michael@0: michael@0: void michael@0: Seer::FreeSpaceInQueue() michael@0: { michael@0: MutexAutoLock lock(mQueueSizeLock); michael@0: MOZ_ASSERT(mQueueSize > 0, "unexpected mQueueSize"); michael@0: mQueueSize--; michael@0: } michael@0: michael@0: // Called from the main thread to initiate predictive actions michael@0: NS_IMETHODIMP michael@0: Seer::Predict(nsIURI *targetURI, nsIURI *sourceURI, SeerPredictReason reason, michael@0: nsILoadContext *loadContext, nsINetworkSeerVerifier *verifier) michael@0: { michael@0: MOZ_ASSERT(NS_IsMainThread(), michael@0: "Seer interface methods must be called on the main thread"); michael@0: michael@0: if (!mInitialized) { michael@0: return NS_ERROR_NOT_AVAILABLE; michael@0: } michael@0: michael@0: if (!mEnabled) { michael@0: return NS_ERROR_NOT_AVAILABLE; michael@0: } michael@0: michael@0: if (loadContext && loadContext->UsePrivateBrowsing()) { michael@0: // Don't want to do anything in PB mode michael@0: return NS_OK; michael@0: } michael@0: michael@0: if (!IsNullOrHttp(targetURI) || !IsNullOrHttp(sourceURI)) { michael@0: // Nothing we can do for non-HTTP[S] schemes michael@0: return NS_OK; michael@0: } michael@0: michael@0: // Ensure we've been given the appropriate arguments for the kind of michael@0: // prediction we're being asked to do michael@0: switch (reason) { michael@0: case nsINetworkSeer::PREDICT_LINK: michael@0: if (!targetURI || !sourceURI) { michael@0: return NS_ERROR_INVALID_ARG; michael@0: } michael@0: // Link hover is a special case where we can predict without hitting the michael@0: // db, so let's go ahead and fire off that prediction here. michael@0: PredictForLink(targetURI, sourceURI, verifier); michael@0: return NS_OK; michael@0: case nsINetworkSeer::PREDICT_LOAD: michael@0: if (!targetURI || sourceURI) { michael@0: return NS_ERROR_INVALID_ARG; michael@0: } michael@0: break; michael@0: case nsINetworkSeer::PREDICT_STARTUP: michael@0: if (targetURI || sourceURI) { michael@0: return NS_ERROR_INVALID_ARG; michael@0: } michael@0: break; michael@0: default: michael@0: return NS_ERROR_INVALID_ARG; michael@0: } michael@0: michael@0: ++mAccumulators->mPredictAttempts; michael@0: nsresult rv = ReserveSpaceInQueue(); michael@0: if (NS_FAILED(rv)) { michael@0: ++mAccumulators->mPredictFullQueue; michael@0: return NS_ERROR_NOT_AVAILABLE; michael@0: } michael@0: michael@0: nsRefPtr event = new SeerPredictionEvent(targetURI, michael@0: sourceURI, michael@0: reason, michael@0: verifier); michael@0: return mIOThread->Dispatch(event, NS_DISPATCH_NORMAL); michael@0: } michael@0: michael@0: // A runnable for updating our information in the database. This must always michael@0: // be dispatched to the seer thread. michael@0: class SeerLearnEvent : public nsRunnable michael@0: { michael@0: public: michael@0: SeerLearnEvent(nsIURI *targetURI, nsIURI *sourceURI, SeerLearnReason reason) michael@0: :mReason(reason) michael@0: { michael@0: MOZ_ASSERT(NS_IsMainThread(), "Creating learn event off main thread"); michael@0: michael@0: mEnqueueTime = TimeStamp::Now(); michael@0: michael@0: targetURI->GetAsciiSpec(mTargetURI.spec); michael@0: ExtractOrigin(targetURI, mTargetURI.origin); michael@0: if (sourceURI) { michael@0: sourceURI->GetAsciiSpec(mSourceURI.spec); michael@0: ExtractOrigin(sourceURI, mSourceURI.origin); michael@0: } michael@0: } michael@0: michael@0: NS_IMETHOD Run() MOZ_OVERRIDE michael@0: { michael@0: MOZ_ASSERT(!NS_IsMainThread(), "Running learn off main thread"); michael@0: michael@0: nsresult rv = NS_OK; michael@0: michael@0: Telemetry::AccumulateTimeDelta(Telemetry::SEER_LEARN_WAIT_TIME, michael@0: mEnqueueTime); michael@0: michael@0: TimeStamp startTime = TimeStamp::Now(); michael@0: michael@0: switch (mReason) { michael@0: case nsINetworkSeer::LEARN_LOAD_TOPLEVEL: michael@0: gSeer->LearnForToplevel(mTargetURI); michael@0: break; michael@0: case nsINetworkSeer::LEARN_LOAD_REDIRECT: michael@0: gSeer->LearnForRedirect(mTargetURI, mSourceURI); michael@0: break; michael@0: case nsINetworkSeer::LEARN_LOAD_SUBRESOURCE: michael@0: gSeer->LearnForSubresource(mTargetURI, mSourceURI); michael@0: break; michael@0: case nsINetworkSeer::LEARN_STARTUP: michael@0: gSeer->LearnForStartup(mTargetURI); michael@0: break; michael@0: default: michael@0: MOZ_ASSERT(false, "Got unexpected value for learn reason"); michael@0: rv = NS_ERROR_UNEXPECTED; michael@0: } michael@0: michael@0: gSeer->FreeSpaceInQueue(); michael@0: michael@0: Telemetry::AccumulateTimeDelta(Telemetry::SEER_LEARN_WORK_TIME, startTime); michael@0: michael@0: gSeer->MaybeScheduleCleanup(); michael@0: michael@0: return rv; michael@0: } michael@0: private: michael@0: Seer::UriInfo mTargetURI; michael@0: Seer::UriInfo mSourceURI; michael@0: SeerLearnReason mReason; michael@0: TimeStamp mEnqueueTime; michael@0: }; michael@0: michael@0: void michael@0: Seer::LearnForToplevel(const UriInfo &uri) michael@0: { michael@0: MOZ_ASSERT(!NS_IsMainThread(), "LearnForToplevel called on main thread."); michael@0: michael@0: if (NS_FAILED(EnsureInitStorage())) { michael@0: return; michael@0: } michael@0: michael@0: PRTime now = PR_Now(); michael@0: michael@0: MaybeLearnForStartup(uri, now); michael@0: michael@0: TopLevelInfo pageInfo; michael@0: TopLevelInfo originInfo; michael@0: bool havePage = LookupTopLevel(QUERY_PAGE, uri.spec, pageInfo); michael@0: bool haveOrigin = LookupTopLevel(QUERY_ORIGIN, uri.origin, originInfo); michael@0: michael@0: if (!havePage) { michael@0: AddTopLevel(QUERY_PAGE, uri.spec, now); michael@0: } else { michael@0: UpdateTopLevel(QUERY_PAGE, pageInfo, now); michael@0: } michael@0: michael@0: if (!haveOrigin) { michael@0: AddTopLevel(QUERY_ORIGIN, uri.origin, now); michael@0: } else { michael@0: UpdateTopLevel(QUERY_ORIGIN, originInfo, now); michael@0: } michael@0: } michael@0: michael@0: // Queries to look up information about a *specific* subresource associated michael@0: // with a *specific* top-level load. michael@0: bool michael@0: Seer::LookupSubresource(QueryType queryType, const int32_t parentId, michael@0: const nsACString &key, SubresourceInfo &info) michael@0: { michael@0: MOZ_ASSERT(!NS_IsMainThread(), "LookupSubresource called on main thread."); michael@0: michael@0: nsCOMPtr stmt; michael@0: if (queryType == QUERY_PAGE) { michael@0: stmt = mStatements.GetCachedStatement( michael@0: NS_LITERAL_CSTRING("SELECT id, hits, last_hit FROM moz_subresources " michael@0: "WHERE pid = :parent_id AND uri = :key;")); michael@0: } else { michael@0: stmt = mStatements.GetCachedStatement( michael@0: NS_LITERAL_CSTRING("SELECT id, hits, last_hit FROM moz_subhosts WHERE " michael@0: "hid = :parent_id AND origin = :key;")); michael@0: } michael@0: NS_ENSURE_TRUE(stmt, false); michael@0: mozStorageStatementScoper scope(stmt); michael@0: michael@0: nsresult rv = stmt->BindInt32ByName(NS_LITERAL_CSTRING("parent_id"), michael@0: parentId); michael@0: NS_ENSURE_SUCCESS(rv, false); michael@0: michael@0: rv = stmt->BindUTF8StringByName(NS_LITERAL_CSTRING("key"), key); michael@0: NS_ENSURE_SUCCESS(rv, false); michael@0: michael@0: bool hasRows; michael@0: rv = stmt->ExecuteStep(&hasRows); michael@0: NS_ENSURE_SUCCESS(rv, false); michael@0: if (!hasRows) { michael@0: return false; michael@0: } michael@0: michael@0: rv = stmt->GetInt32(0, &info.id); michael@0: NS_ENSURE_SUCCESS(rv, false); michael@0: michael@0: rv = stmt->GetInt32(1, &info.hitCount); michael@0: NS_ENSURE_SUCCESS(rv, false); michael@0: michael@0: rv = stmt->GetInt64(2, &info.lastHit); michael@0: NS_ENSURE_SUCCESS(rv, false); michael@0: michael@0: return true; michael@0: } michael@0: michael@0: // Add information about a new subresource associated with a top-level load. michael@0: void michael@0: Seer::AddSubresource(QueryType queryType, const int32_t parentId, michael@0: const nsACString &key, const PRTime now) michael@0: { michael@0: MOZ_ASSERT(!NS_IsMainThread(), "AddSubresource called on main thread."); michael@0: michael@0: nsCOMPtr stmt; michael@0: if (queryType == QUERY_PAGE) { michael@0: stmt = mStatements.GetCachedStatement( michael@0: NS_LITERAL_CSTRING("INSERT INTO moz_subresources " michael@0: "(pid, uri, hits, last_hit) VALUES " michael@0: "(:parent_id, :key, 1, :now);")); michael@0: } else { michael@0: stmt = mStatements.GetCachedStatement( michael@0: NS_LITERAL_CSTRING("INSERT INTO moz_subhosts " michael@0: "(hid, origin, hits, last_hit) VALUES " michael@0: "(:parent_id, :key, 1, :now);")); michael@0: } michael@0: if (!stmt) { michael@0: return; michael@0: } michael@0: mozStorageStatementScoper scope(stmt); michael@0: michael@0: nsresult rv = stmt->BindInt32ByName(NS_LITERAL_CSTRING("parent_id"), michael@0: parentId); michael@0: RETURN_IF_FAILED(rv); michael@0: michael@0: rv = stmt->BindUTF8StringByName(NS_LITERAL_CSTRING("key"), key); michael@0: RETURN_IF_FAILED(rv); michael@0: michael@0: rv = stmt->BindInt64ByName(NS_LITERAL_CSTRING("now"), now); michael@0: RETURN_IF_FAILED(rv); michael@0: michael@0: rv = stmt->Execute(); michael@0: } michael@0: michael@0: // Update the information about a particular subresource associated with a michael@0: // top-level load michael@0: void michael@0: Seer::UpdateSubresource(QueryType queryType, const SubresourceInfo &info, michael@0: const PRTime now) michael@0: { michael@0: MOZ_ASSERT(!NS_IsMainThread(), "UpdateSubresource called on main thread."); michael@0: michael@0: nsCOMPtr stmt; michael@0: if (queryType == QUERY_PAGE) { michael@0: stmt = mStatements.GetCachedStatement( michael@0: NS_LITERAL_CSTRING("UPDATE moz_subresources SET hits = :hit_count, " michael@0: "last_hit = :now WHERE id = :id;")); michael@0: } else { michael@0: stmt = mStatements.GetCachedStatement( michael@0: NS_LITERAL_CSTRING("UPDATE moz_subhosts SET hits = :hit_count, " michael@0: "last_hit = :now WHERE id = :id;")); michael@0: } michael@0: if (!stmt) { michael@0: return; michael@0: } michael@0: mozStorageStatementScoper scope(stmt); michael@0: michael@0: nsresult rv = stmt->BindInt32ByName(NS_LITERAL_CSTRING("hit_count"), michael@0: info.hitCount + 1); michael@0: RETURN_IF_FAILED(rv); michael@0: michael@0: rv = stmt->BindInt64ByName(NS_LITERAL_CSTRING("now"), now); michael@0: RETURN_IF_FAILED(rv); michael@0: michael@0: rv = stmt->BindInt32ByName(NS_LITERAL_CSTRING("id"), info.id); michael@0: RETURN_IF_FAILED(rv); michael@0: michael@0: rv = stmt->Execute(); michael@0: } michael@0: michael@0: // Called when a subresource has been hit from a top-level load. Uses the two michael@0: // helper functions above to update the database appropriately. michael@0: void michael@0: Seer::LearnForSubresource(const UriInfo &targetURI, const UriInfo &sourceURI) michael@0: { michael@0: MOZ_ASSERT(!NS_IsMainThread(), "LearnForSubresource called on main thread."); michael@0: michael@0: if (NS_FAILED(EnsureInitStorage())) { michael@0: return; michael@0: } michael@0: michael@0: TopLevelInfo pageInfo, originInfo; michael@0: bool havePage = LookupTopLevel(QUERY_PAGE, sourceURI.spec, pageInfo); michael@0: bool haveOrigin = LookupTopLevel(QUERY_ORIGIN, sourceURI.origin, michael@0: originInfo); michael@0: michael@0: if (!havePage && !haveOrigin) { michael@0: // Nothing to do, since we know nothing about the top level resource michael@0: return; michael@0: } michael@0: michael@0: SubresourceInfo resourceInfo; michael@0: bool haveResource = false; michael@0: if (havePage) { michael@0: haveResource = LookupSubresource(QUERY_PAGE, pageInfo.id, targetURI.spec, michael@0: resourceInfo); michael@0: } michael@0: michael@0: SubresourceInfo hostInfo; michael@0: bool haveHost = false; michael@0: if (haveOrigin) { michael@0: haveHost = LookupSubresource(QUERY_ORIGIN, originInfo.id, targetURI.origin, michael@0: hostInfo); michael@0: } michael@0: michael@0: PRTime now = PR_Now(); michael@0: michael@0: if (haveResource) { michael@0: UpdateSubresource(QUERY_PAGE, resourceInfo, now); michael@0: } else if (havePage) { michael@0: AddSubresource(QUERY_PAGE, pageInfo.id, targetURI.spec, now); michael@0: } michael@0: // Can't add a subresource to a page we don't have in our db. michael@0: michael@0: if (haveHost) { michael@0: UpdateSubresource(QUERY_ORIGIN, hostInfo, now); michael@0: } else if (haveOrigin) { michael@0: AddSubresource(QUERY_ORIGIN, originInfo.id, targetURI.origin, now); michael@0: } michael@0: // Can't add a subhost to a host we don't have in our db michael@0: } michael@0: michael@0: // This is called when a top-level loaded ended up redirecting to a different michael@0: // URI so we can keep track of that fact. michael@0: void michael@0: Seer::LearnForRedirect(const UriInfo &targetURI, const UriInfo &sourceURI) michael@0: { michael@0: MOZ_ASSERT(!NS_IsMainThread(), "LearnForRedirect called on main thread."); michael@0: michael@0: if (NS_FAILED(EnsureInitStorage())) { michael@0: return; michael@0: } michael@0: michael@0: PRTime now = PR_Now(); michael@0: nsresult rv; michael@0: michael@0: nsCOMPtr getPage = mStatements.GetCachedStatement( michael@0: NS_LITERAL_CSTRING("SELECT id FROM moz_pages WHERE uri = :spec;")); michael@0: if (!getPage) { michael@0: return; michael@0: } michael@0: mozStorageStatementScoper scopedPage(getPage); michael@0: michael@0: // look up source in moz_pages michael@0: rv = getPage->BindUTF8StringByName(NS_LITERAL_CSTRING("spec"), michael@0: sourceURI.spec); michael@0: RETURN_IF_FAILED(rv); michael@0: michael@0: bool hasRows; michael@0: rv = getPage->ExecuteStep(&hasRows); michael@0: if (NS_FAILED(rv) || !hasRows) { michael@0: return; michael@0: } michael@0: michael@0: int32_t pageId; michael@0: rv = getPage->GetInt32(0, &pageId); michael@0: RETURN_IF_FAILED(rv); michael@0: michael@0: nsCOMPtr getRedirect = mStatements.GetCachedStatement( michael@0: NS_LITERAL_CSTRING("SELECT id, hits FROM moz_redirects WHERE " michael@0: "pid = :page_id AND uri = :spec;")); michael@0: if (!getRedirect) { michael@0: return; michael@0: } michael@0: mozStorageStatementScoper scopedRedirect(getRedirect); michael@0: michael@0: rv = getRedirect->BindInt32ByName(NS_LITERAL_CSTRING("page_id"), pageId); michael@0: RETURN_IF_FAILED(rv); michael@0: michael@0: rv = getRedirect->BindUTF8StringByName(NS_LITERAL_CSTRING("spec"), michael@0: targetURI.spec); michael@0: RETURN_IF_FAILED(rv); michael@0: michael@0: rv = getRedirect->ExecuteStep(&hasRows); michael@0: RETURN_IF_FAILED(rv); michael@0: michael@0: if (!hasRows) { michael@0: // This is the first time we've seen this top-level redirect to this URI michael@0: nsCOMPtr addRedirect = mStatements.GetCachedStatement( michael@0: NS_LITERAL_CSTRING("INSERT INTO moz_redirects " michael@0: "(pid, uri, origin, hits, last_hit) VALUES " michael@0: "(:page_id, :spec, :origin, 1, :now);")); michael@0: if (!addRedirect) { michael@0: return; michael@0: } michael@0: mozStorageStatementScoper scopedAdd(addRedirect); michael@0: michael@0: rv = addRedirect->BindInt32ByName(NS_LITERAL_CSTRING("page_id"), pageId); michael@0: RETURN_IF_FAILED(rv); michael@0: michael@0: rv = addRedirect->BindUTF8StringByName(NS_LITERAL_CSTRING("spec"), michael@0: targetURI.spec); michael@0: RETURN_IF_FAILED(rv); michael@0: michael@0: rv = addRedirect->BindUTF8StringByName(NS_LITERAL_CSTRING("origin"), michael@0: targetURI.origin); michael@0: RETURN_IF_FAILED(rv); michael@0: michael@0: rv = addRedirect->BindInt64ByName(NS_LITERAL_CSTRING("now"), now); michael@0: RETURN_IF_FAILED(rv); michael@0: michael@0: rv = addRedirect->Execute(); michael@0: } else { michael@0: // We've seen this redirect before michael@0: int32_t redirId, hits; michael@0: rv = getRedirect->GetInt32(0, &redirId); michael@0: RETURN_IF_FAILED(rv); michael@0: michael@0: rv = getRedirect->GetInt32(1, &hits); michael@0: RETURN_IF_FAILED(rv); michael@0: michael@0: nsCOMPtr updateRedirect = michael@0: mStatements.GetCachedStatement( michael@0: NS_LITERAL_CSTRING("UPDATE moz_redirects SET hits = :hits, " michael@0: "last_hit = :now WHERE id = :redir;")); michael@0: if (!updateRedirect) { michael@0: return; michael@0: } michael@0: mozStorageStatementScoper scopedUpdate(updateRedirect); michael@0: michael@0: rv = updateRedirect->BindInt32ByName(NS_LITERAL_CSTRING("hits"), hits + 1); michael@0: RETURN_IF_FAILED(rv); michael@0: michael@0: rv = updateRedirect->BindInt64ByName(NS_LITERAL_CSTRING("now"), now); michael@0: RETURN_IF_FAILED(rv); michael@0: michael@0: rv = updateRedirect->BindInt32ByName(NS_LITERAL_CSTRING("redir"), redirId); michael@0: RETURN_IF_FAILED(rv); michael@0: michael@0: updateRedirect->Execute(); michael@0: } michael@0: } michael@0: michael@0: // Add information about a top-level load to our list of startup pages michael@0: void michael@0: Seer::LearnForStartup(const UriInfo &uri) michael@0: { michael@0: MOZ_ASSERT(!NS_IsMainThread(), "LearnForStartup called on main thread."); michael@0: michael@0: if (NS_FAILED(EnsureInitStorage())) { michael@0: return; michael@0: } michael@0: michael@0: nsCOMPtr getPage = mStatements.GetCachedStatement( michael@0: NS_LITERAL_CSTRING("SELECT id, hits FROM moz_startup_pages WHERE " michael@0: "uri = :origin;")); michael@0: if (!getPage) { michael@0: return; michael@0: } michael@0: mozStorageStatementScoper scopedPage(getPage); michael@0: nsresult rv; michael@0: michael@0: rv = getPage->BindUTF8StringByName(NS_LITERAL_CSTRING("origin"), uri.origin); michael@0: RETURN_IF_FAILED(rv); michael@0: michael@0: bool hasRows; michael@0: rv = getPage->ExecuteStep(&hasRows); michael@0: RETURN_IF_FAILED(rv); michael@0: michael@0: if (hasRows) { michael@0: // We've loaded this page on startup before michael@0: int32_t pageId, hitCount; michael@0: michael@0: rv = getPage->GetInt32(0, &pageId); michael@0: RETURN_IF_FAILED(rv); michael@0: michael@0: rv = getPage->GetInt32(1, &hitCount); michael@0: RETURN_IF_FAILED(rv); michael@0: michael@0: nsCOMPtr updatePage = mStatements.GetCachedStatement( michael@0: NS_LITERAL_CSTRING("UPDATE moz_startup_pages SET hits = :hit_count, " michael@0: "last_hit = :startup_time WHERE id = :page_id;")); michael@0: if (!updatePage) { michael@0: return; michael@0: } michael@0: mozStorageStatementScoper scopedUpdate(updatePage); michael@0: michael@0: rv = updatePage->BindInt32ByName(NS_LITERAL_CSTRING("hit_count"), michael@0: hitCount + 1); michael@0: RETURN_IF_FAILED(rv); michael@0: michael@0: rv = updatePage->BindInt64ByName(NS_LITERAL_CSTRING("startup_time"), michael@0: mStartupTime); michael@0: RETURN_IF_FAILED(rv); michael@0: michael@0: rv = updatePage->BindInt32ByName(NS_LITERAL_CSTRING("page_id"), pageId); michael@0: RETURN_IF_FAILED(rv); michael@0: michael@0: updatePage->Execute(); michael@0: } else { michael@0: // New startup page michael@0: nsCOMPtr addPage = mStatements.GetCachedStatement( michael@0: NS_LITERAL_CSTRING("INSERT INTO moz_startup_pages (uri, hits, " michael@0: "last_hit) VALUES (:origin, 1, :startup_time);")); michael@0: if (!addPage) { michael@0: return; michael@0: } michael@0: mozStorageStatementScoper scopedAdd(addPage); michael@0: rv = addPage->BindUTF8StringByName(NS_LITERAL_CSTRING("origin"), michael@0: uri.origin); michael@0: RETURN_IF_FAILED(rv); michael@0: michael@0: rv = addPage->BindInt64ByName(NS_LITERAL_CSTRING("startup_time"), michael@0: mStartupTime); michael@0: RETURN_IF_FAILED(rv); michael@0: michael@0: addPage->Execute(); michael@0: } michael@0: } michael@0: michael@0: // Called from the main thread to update the database michael@0: NS_IMETHODIMP michael@0: Seer::Learn(nsIURI *targetURI, nsIURI *sourceURI, SeerLearnReason reason, michael@0: nsILoadContext *loadContext) michael@0: { michael@0: MOZ_ASSERT(NS_IsMainThread(), michael@0: "Seer interface methods must be called on the main thread"); michael@0: michael@0: if (!mInitialized) { michael@0: return NS_ERROR_NOT_AVAILABLE; michael@0: } michael@0: michael@0: if (!mEnabled) { michael@0: return NS_ERROR_NOT_AVAILABLE; michael@0: } michael@0: michael@0: if (loadContext && loadContext->UsePrivateBrowsing()) { michael@0: // Don't want to do anything in PB mode michael@0: return NS_OK; michael@0: } michael@0: michael@0: if (!IsNullOrHttp(targetURI) || !IsNullOrHttp(sourceURI)) { michael@0: return NS_ERROR_INVALID_ARG; michael@0: } michael@0: michael@0: switch (reason) { michael@0: case nsINetworkSeer::LEARN_LOAD_TOPLEVEL: michael@0: case nsINetworkSeer::LEARN_STARTUP: michael@0: if (!targetURI || sourceURI) { michael@0: return NS_ERROR_INVALID_ARG; michael@0: } michael@0: break; michael@0: case nsINetworkSeer::LEARN_LOAD_REDIRECT: michael@0: case nsINetworkSeer::LEARN_LOAD_SUBRESOURCE: michael@0: if (!targetURI || !sourceURI) { michael@0: return NS_ERROR_INVALID_ARG; michael@0: } michael@0: break; michael@0: default: michael@0: return NS_ERROR_INVALID_ARG; michael@0: } michael@0: michael@0: ++mAccumulators->mLearnAttempts; michael@0: nsresult rv = ReserveSpaceInQueue(); michael@0: if (NS_FAILED(rv)) { michael@0: ++mAccumulators->mLearnFullQueue; michael@0: return NS_ERROR_NOT_AVAILABLE; michael@0: } michael@0: michael@0: nsRefPtr event = new SeerLearnEvent(targetURI, sourceURI, michael@0: reason); michael@0: return mIOThread->Dispatch(event, NS_DISPATCH_NORMAL); michael@0: } michael@0: michael@0: // Runnable to clear out the database. Dispatched from the main thread to the michael@0: // seer thread michael@0: class SeerResetEvent : public nsRunnable michael@0: { michael@0: public: michael@0: SeerResetEvent() michael@0: { } michael@0: michael@0: NS_IMETHOD Run() MOZ_OVERRIDE michael@0: { michael@0: MOZ_ASSERT(!NS_IsMainThread(), "Running reset on main thread"); michael@0: michael@0: gSeer->ResetInternal(); michael@0: michael@0: return NS_OK; michael@0: } michael@0: }; michael@0: michael@0: // Helper that actually does the database wipe. michael@0: void michael@0: Seer::ResetInternal() michael@0: { michael@0: MOZ_ASSERT(!NS_IsMainThread(), "Resetting db on main thread"); michael@0: michael@0: nsresult rv = EnsureInitStorage(); michael@0: RETURN_IF_FAILED(rv); michael@0: michael@0: mDB->ExecuteSimpleSQL(NS_LITERAL_CSTRING("DELETE FROM moz_redirects;")); michael@0: mDB->ExecuteSimpleSQL(NS_LITERAL_CSTRING("DELETE FROM moz_startup_pages;")); michael@0: mDB->ExecuteSimpleSQL(NS_LITERAL_CSTRING("DELETE FROM moz_startups;")); michael@0: michael@0: // These cascade to moz_subresources and moz_subhosts, respectively. michael@0: mDB->ExecuteSimpleSQL(NS_LITERAL_CSTRING("DELETE FROM moz_pages;")); michael@0: mDB->ExecuteSimpleSQL(NS_LITERAL_CSTRING("DELETE FROM moz_hosts;")); michael@0: michael@0: VacuumDatabase(); michael@0: michael@0: // Go ahead and ensure this is flushed to disk michael@0: CommitTransaction(); michael@0: BeginTransaction(); michael@0: } michael@0: michael@0: // Called on the main thread to clear out all our knowledge. Tabula Rasa FTW! michael@0: NS_IMETHODIMP michael@0: Seer::Reset() michael@0: { michael@0: MOZ_ASSERT(NS_IsMainThread(), michael@0: "Seer interface methods must be called on the main thread"); michael@0: michael@0: if (!mInitialized) { michael@0: return NS_ERROR_NOT_AVAILABLE; michael@0: } michael@0: michael@0: if (!mEnabled) { michael@0: return NS_OK; michael@0: } michael@0: michael@0: nsRefPtr event = new SeerResetEvent(); michael@0: return mIOThread->Dispatch(event, NS_DISPATCH_NORMAL); michael@0: } michael@0: michael@0: class SeerCleanupEvent : public nsRunnable michael@0: { michael@0: public: michael@0: NS_IMETHOD Run() MOZ_OVERRIDE michael@0: { michael@0: gSeer->Cleanup(); michael@0: gSeer->mCleanupScheduled = false; michael@0: return NS_OK; michael@0: } michael@0: }; michael@0: michael@0: // Returns the current size (in bytes) of the db file on disk michael@0: int64_t michael@0: Seer::GetDBFileSize() michael@0: { michael@0: MOZ_ASSERT(!NS_IsMainThread(), "GetDBFileSize called on main thread!"); michael@0: michael@0: nsresult rv = EnsureInitStorage(); michael@0: if (NS_FAILED(rv)) { michael@0: SEER_LOG(("GetDBFileSize called without db available!")); michael@0: return 0; michael@0: } michael@0: michael@0: CommitTransaction(); michael@0: michael@0: nsCOMPtr countStmt = mStatements.GetCachedStatement( michael@0: NS_LITERAL_CSTRING("PRAGMA page_count;")); michael@0: if (!countStmt) { michael@0: return 0; michael@0: } michael@0: mozStorageStatementScoper scopedCount(countStmt); michael@0: bool hasRows; michael@0: rv = countStmt->ExecuteStep(&hasRows); michael@0: if (NS_FAILED(rv) || !hasRows) { michael@0: return 0; michael@0: } michael@0: int64_t pageCount; michael@0: rv = countStmt->GetInt64(0, &pageCount); michael@0: if (NS_FAILED(rv)) { michael@0: return 0; michael@0: } michael@0: michael@0: nsCOMPtr sizeStmt = mStatements.GetCachedStatement( michael@0: NS_LITERAL_CSTRING("PRAGMA page_size;")); michael@0: if (!sizeStmt) { michael@0: return 0; michael@0: } michael@0: mozStorageStatementScoper scopedSize(sizeStmt); michael@0: rv = sizeStmt->ExecuteStep(&hasRows); michael@0: if (NS_FAILED(rv) || !hasRows) { michael@0: return 0; michael@0: } michael@0: int64_t pageSize; michael@0: rv = sizeStmt->GetInt64(0, &pageSize); michael@0: if (NS_FAILED(rv)) { michael@0: return 0; michael@0: } michael@0: michael@0: BeginTransaction(); michael@0: michael@0: return pageCount * pageSize; michael@0: } michael@0: michael@0: // Returns the size (in bytes) that the db file will consume on disk AFTER we michael@0: // vacuum the db. michael@0: int64_t michael@0: Seer::GetDBFileSizeAfterVacuum() michael@0: { michael@0: MOZ_ASSERT(!NS_IsMainThread(), "GetDBFileSizeAfterVacuum called on main thread!"); michael@0: michael@0: CommitTransaction(); michael@0: michael@0: nsCOMPtr countStmt = mStatements.GetCachedStatement( michael@0: NS_LITERAL_CSTRING("PRAGMA page_count;")); michael@0: if (!countStmt) { michael@0: return 0; michael@0: } michael@0: mozStorageStatementScoper scopedCount(countStmt); michael@0: bool hasRows; michael@0: nsresult rv = countStmt->ExecuteStep(&hasRows); michael@0: if (NS_FAILED(rv) || !hasRows) { michael@0: return 0; michael@0: } michael@0: int64_t pageCount; michael@0: rv = countStmt->GetInt64(0, &pageCount); michael@0: if (NS_FAILED(rv)) { michael@0: return 0; michael@0: } michael@0: michael@0: nsCOMPtr sizeStmt = mStatements.GetCachedStatement( michael@0: NS_LITERAL_CSTRING("PRAGMA page_size;")); michael@0: if (!sizeStmt) { michael@0: return 0; michael@0: } michael@0: mozStorageStatementScoper scopedSize(sizeStmt); michael@0: rv = sizeStmt->ExecuteStep(&hasRows); michael@0: if (NS_FAILED(rv) || !hasRows) { michael@0: return 0; michael@0: } michael@0: int64_t pageSize; michael@0: rv = sizeStmt->GetInt64(0, &pageSize); michael@0: if (NS_FAILED(rv)) { michael@0: return 0; michael@0: } michael@0: michael@0: nsCOMPtr freeStmt = mStatements.GetCachedStatement( michael@0: NS_LITERAL_CSTRING("PRAGMA freelist_count;")); michael@0: if (!freeStmt) { michael@0: return 0; michael@0: } michael@0: mozStorageStatementScoper scopedFree(freeStmt); michael@0: rv = freeStmt->ExecuteStep(&hasRows); michael@0: if (NS_FAILED(rv) || !hasRows) { michael@0: return 0; michael@0: } michael@0: int64_t freelistCount; michael@0: rv = freeStmt->GetInt64(0, &freelistCount); michael@0: if (NS_FAILED(rv)) { michael@0: return 0; michael@0: } michael@0: michael@0: BeginTransaction(); michael@0: michael@0: return (pageCount - freelistCount) * pageSize; michael@0: } michael@0: michael@0: void michael@0: Seer::MaybeScheduleCleanup() michael@0: { michael@0: MOZ_ASSERT(!NS_IsMainThread(), "MaybeScheduleCleanup called on main thread!"); michael@0: michael@0: // This is a little racy, but it's a nice little shutdown optimization if the michael@0: // race works out the right way. michael@0: if (!mInitialized) { michael@0: return; michael@0: } michael@0: michael@0: if (mCleanupScheduled) { michael@0: Telemetry::Accumulate(Telemetry::SEER_CLEANUP_SCHEDULED, false); michael@0: return; michael@0: } michael@0: michael@0: int64_t dbFileSize = GetDBFileSize(); michael@0: if (dbFileSize < mMaxDBSize) { michael@0: Telemetry::Accumulate(Telemetry::SEER_CLEANUP_SCHEDULED, false); michael@0: return; michael@0: } michael@0: michael@0: mCleanupScheduled = true; michael@0: michael@0: nsRefPtr event = new SeerCleanupEvent(); michael@0: nsresult rv = mIOThread->Dispatch(event, NS_DISPATCH_NORMAL); michael@0: if (NS_FAILED(rv)) { michael@0: mCleanupScheduled = false; michael@0: Telemetry::Accumulate(Telemetry::SEER_CLEANUP_SCHEDULED, false); michael@0: } else { michael@0: Telemetry::Accumulate(Telemetry::SEER_CLEANUP_SCHEDULED, true); michael@0: } michael@0: } michael@0: michael@0: #ifndef ANDROID michael@0: static const long long CLEANUP_CUTOFF = ONE_MONTH; michael@0: #else michael@0: static const long long CLEANUP_CUTOFF = ONE_WEEK; michael@0: #endif michael@0: michael@0: void michael@0: Seer::CleanupOrigins(PRTime now) michael@0: { michael@0: PRTime cutoff = now - CLEANUP_CUTOFF; michael@0: michael@0: nsCOMPtr deleteOrigins = mStatements.GetCachedStatement( michael@0: NS_LITERAL_CSTRING("DELETE FROM moz_hosts WHERE last_load <= :cutoff")); michael@0: if (!deleteOrigins) { michael@0: return; michael@0: } michael@0: mozStorageStatementScoper scopedOrigins(deleteOrigins); michael@0: michael@0: nsresult rv = deleteOrigins->BindInt32ByName(NS_LITERAL_CSTRING("cutoff"), michael@0: cutoff); michael@0: RETURN_IF_FAILED(rv); michael@0: michael@0: deleteOrigins->Execute(); michael@0: } michael@0: michael@0: void michael@0: Seer::CleanupStartupPages(PRTime now) michael@0: { michael@0: PRTime cutoff = now - ONE_WEEK; michael@0: michael@0: nsCOMPtr deletePages = mStatements.GetCachedStatement( michael@0: NS_LITERAL_CSTRING("DELETE FROM moz_startup_pages WHERE " michael@0: "last_hit <= :cutoff")); michael@0: if (!deletePages) { michael@0: return; michael@0: } michael@0: mozStorageStatementScoper scopedPages(deletePages); michael@0: michael@0: nsresult rv = deletePages->BindInt32ByName(NS_LITERAL_CSTRING("cutoff"), michael@0: cutoff); michael@0: RETURN_IF_FAILED(rv); michael@0: michael@0: deletePages->Execute(); michael@0: } michael@0: michael@0: int32_t michael@0: Seer::GetSubresourceCount() michael@0: { michael@0: nsCOMPtr count = mStatements.GetCachedStatement( michael@0: NS_LITERAL_CSTRING("SELECT COUNT(id) FROM moz_subresources")); michael@0: if (!count) { michael@0: return 0; michael@0: } michael@0: mozStorageStatementScoper scopedCount(count); michael@0: michael@0: bool hasRows; michael@0: nsresult rv = count->ExecuteStep(&hasRows); michael@0: if (NS_FAILED(rv) || !hasRows) { michael@0: return 0; michael@0: } michael@0: michael@0: int32_t subresourceCount = 0; michael@0: count->GetInt32(0, &subresourceCount); michael@0: michael@0: return subresourceCount; michael@0: } michael@0: michael@0: void michael@0: Seer::Cleanup() michael@0: { michael@0: MOZ_ASSERT(!NS_IsMainThread(), "Seer::Cleanup called on main thread!"); michael@0: michael@0: nsresult rv = EnsureInitStorage(); michael@0: if (NS_FAILED(rv)) { michael@0: return; michael@0: } michael@0: michael@0: int64_t dbFileSize = GetDBFileSize(); michael@0: float preservePercentage = static_cast(mPreservePercentage) / 100.0; michael@0: int64_t evictionCutoff = static_cast(mMaxDBSize) * preservePercentage; michael@0: if (dbFileSize < evictionCutoff) { michael@0: return; michael@0: } michael@0: michael@0: CommitTransaction(); michael@0: BeginTransaction(); michael@0: michael@0: PRTime now = PR_Now(); michael@0: if (mLastCleanupTime) { michael@0: Telemetry::Accumulate(Telemetry::SEER_CLEANUP_DELTA, michael@0: (now - mLastCleanupTime) / 1000); michael@0: } michael@0: mLastCleanupTime = now; michael@0: michael@0: CleanupOrigins(now); michael@0: CleanupStartupPages(now); michael@0: michael@0: dbFileSize = GetDBFileSizeAfterVacuum(); michael@0: if (dbFileSize < evictionCutoff) { michael@0: // We've deleted enough stuff, time to free up the disk space and be on michael@0: // our way. michael@0: VacuumDatabase(); michael@0: Telemetry::Accumulate(Telemetry::SEER_CLEANUP_SUCCEEDED, true); michael@0: Telemetry::Accumulate(Telemetry::SEER_CLEANUP_TIME, michael@0: (PR_Now() - mLastCleanupTime) / 1000); michael@0: return; michael@0: } michael@0: michael@0: bool canDelete = true; michael@0: while (canDelete && (dbFileSize >= evictionCutoff)) { michael@0: int32_t subresourceCount = GetSubresourceCount(); michael@0: if (!subresourceCount) { michael@0: canDelete = false; michael@0: break; michael@0: } michael@0: michael@0: // DB size scales pretty much linearly with the number of rows in michael@0: // moz_subresources, so we can guess how many rows we need to delete pretty michael@0: // accurately. michael@0: float percentNeeded = static_cast(dbFileSize - evictionCutoff) / michael@0: static_cast(dbFileSize); michael@0: michael@0: int32_t subresourcesToDelete = static_cast(percentNeeded * subresourceCount); michael@0: if (!subresourcesToDelete) { michael@0: // We're getting pretty close to nothing here, anyway, so we may as well michael@0: // just trash it all. This delete cascades to moz_subresources, as well. michael@0: rv = mDB->ExecuteSimpleSQL(NS_LITERAL_CSTRING("DELETE FROM moz_pages;")); michael@0: if (NS_FAILED(rv)) { michael@0: canDelete = false; michael@0: break; michael@0: } michael@0: } else { michael@0: nsCOMPtr deleteStatement = mStatements.GetCachedStatement( michael@0: NS_LITERAL_CSTRING("DELETE FROM moz_subresources WHERE id IN " michael@0: "(SELECT id FROM moz_subresources ORDER BY " michael@0: "last_hit ASC LIMIT :limit);")); michael@0: if (!deleteStatement) { michael@0: canDelete = false; michael@0: break; michael@0: } michael@0: mozStorageStatementScoper scopedDelete(deleteStatement); michael@0: michael@0: rv = deleteStatement->BindInt32ByName(NS_LITERAL_CSTRING("limit"), michael@0: subresourcesToDelete); michael@0: if (NS_FAILED(rv)) { michael@0: canDelete = false; michael@0: break; michael@0: } michael@0: michael@0: rv = deleteStatement->Execute(); michael@0: if (NS_FAILED(rv)) { michael@0: canDelete = false; michael@0: break; michael@0: } michael@0: michael@0: // Now we clean up pages that no longer reference any subresources michael@0: rv = mDB->ExecuteSimpleSQL( michael@0: NS_LITERAL_CSTRING("DELETE FROM moz_pages WHERE id NOT IN " michael@0: "(SELECT DISTINCT(pid) FROM moz_subresources);")); michael@0: if (NS_FAILED(rv)) { michael@0: canDelete = false; michael@0: break; michael@0: } michael@0: } michael@0: michael@0: if (canDelete) { michael@0: dbFileSize = GetDBFileSizeAfterVacuum(); michael@0: } michael@0: } michael@0: michael@0: if (!canDelete || (dbFileSize >= evictionCutoff)) { michael@0: // Last-ditch effort to free up space michael@0: ResetInternal(); michael@0: Telemetry::Accumulate(Telemetry::SEER_CLEANUP_SUCCEEDED, false); michael@0: } else { michael@0: // We do this to actually free up the space on disk michael@0: VacuumDatabase(); michael@0: Telemetry::Accumulate(Telemetry::SEER_CLEANUP_SUCCEEDED, true); michael@0: } michael@0: Telemetry::Accumulate(Telemetry::SEER_CLEANUP_TIME, michael@0: (PR_Now() - mLastCleanupTime) / 1000); michael@0: } michael@0: michael@0: void michael@0: Seer::VacuumDatabase() michael@0: { michael@0: MOZ_ASSERT(!NS_IsMainThread(), "VacuumDatabase called on main thread!"); michael@0: michael@0: CommitTransaction(); michael@0: mDB->ExecuteSimpleSQL(NS_LITERAL_CSTRING("VACUUM;")); michael@0: BeginTransaction(); michael@0: } michael@0: michael@0: #ifdef SEER_TESTS michael@0: class SeerPrepareForDnsTestEvent : public nsRunnable michael@0: { michael@0: public: michael@0: SeerPrepareForDnsTestEvent(int64_t timestamp, const char *uri) michael@0: :mTimestamp(timestamp) michael@0: ,mUri(uri) michael@0: { } michael@0: michael@0: NS_IMETHOD Run() MOZ_OVERRIDE michael@0: { michael@0: MOZ_ASSERT(!NS_IsMainThread(), "Preparing for DNS Test on main thread!"); michael@0: gSeer->PrepareForDnsTestInternal(mTimestamp, mUri); michael@0: return NS_OK; michael@0: } michael@0: michael@0: private: michael@0: int64_t mTimestamp; michael@0: nsAutoCString mUri; michael@0: }; michael@0: michael@0: void michael@0: Seer::PrepareForDnsTestInternal(int64_t timestamp, const nsACString &uri) michael@0: { michael@0: nsCOMPtr update = mStatements.GetCachedStatement( michael@0: NS_LITERAL_CSTRING("UPDATE moz_subresources SET last_hit = :timestamp, " michael@0: "hits = 2 WHERE uri = :uri;")); michael@0: if (!update) { michael@0: return; michael@0: } michael@0: mozStorageStatementScoper scopedUpdate(update); michael@0: michael@0: nsresult rv = update->BindInt64ByName(NS_LITERAL_CSTRING("timestamp"), michael@0: timestamp); michael@0: RETURN_IF_FAILED(rv); michael@0: michael@0: rv = update->BindUTF8StringByName(NS_LITERAL_CSTRING("uri"), uri); michael@0: RETURN_IF_FAILED(rv); michael@0: michael@0: update->Execute(); michael@0: } michael@0: #endif michael@0: michael@0: NS_IMETHODIMP michael@0: Seer::PrepareForDnsTest(int64_t timestamp, const char *uri) michael@0: { michael@0: #ifdef SEER_TESTS michael@0: MOZ_ASSERT(NS_IsMainThread(), michael@0: "Seer interface methods must be called on the main thread"); michael@0: michael@0: if (!mInitialized) { michael@0: return NS_ERROR_NOT_AVAILABLE; michael@0: } michael@0: michael@0: nsRefPtr event = michael@0: new SeerPrepareForDnsTestEvent(timestamp, uri); michael@0: return mIOThread->Dispatch(event, NS_DISPATCH_NORMAL); michael@0: #else michael@0: return NS_ERROR_NOT_IMPLEMENTED; michael@0: #endif michael@0: } michael@0: michael@0: // Helper functions to make using the seer easier from native code michael@0: michael@0: static nsresult michael@0: EnsureGlobalSeer(nsINetworkSeer **aSeer) michael@0: { michael@0: nsresult rv; michael@0: nsCOMPtr seer = do_GetService("@mozilla.org/network/seer;1", michael@0: &rv); michael@0: NS_ENSURE_SUCCESS(rv, rv); michael@0: michael@0: NS_IF_ADDREF(*aSeer = seer); michael@0: return NS_OK; michael@0: } michael@0: michael@0: nsresult michael@0: SeerPredict(nsIURI *targetURI, nsIURI *sourceURI, SeerPredictReason reason, michael@0: nsILoadContext *loadContext, nsINetworkSeerVerifier *verifier) michael@0: { michael@0: if (!IsNullOrHttp(targetURI) || !IsNullOrHttp(sourceURI)) { michael@0: return NS_OK; michael@0: } michael@0: michael@0: nsCOMPtr seer; michael@0: nsresult rv = EnsureGlobalSeer(getter_AddRefs(seer)); michael@0: NS_ENSURE_SUCCESS(rv, rv); michael@0: michael@0: return seer->Predict(targetURI, sourceURI, reason, loadContext, verifier); michael@0: } michael@0: michael@0: nsresult michael@0: SeerLearn(nsIURI *targetURI, nsIURI *sourceURI, SeerLearnReason reason, michael@0: nsILoadContext *loadContext) michael@0: { michael@0: if (!IsNullOrHttp(targetURI) || !IsNullOrHttp(sourceURI)) { michael@0: return NS_OK; michael@0: } michael@0: michael@0: nsCOMPtr seer; michael@0: nsresult rv = EnsureGlobalSeer(getter_AddRefs(seer)); michael@0: NS_ENSURE_SUCCESS(rv, rv); michael@0: michael@0: return seer->Learn(targetURI, sourceURI, reason, loadContext); michael@0: } michael@0: michael@0: nsresult michael@0: SeerLearn(nsIURI *targetURI, nsIURI *sourceURI, SeerLearnReason reason, michael@0: nsILoadGroup *loadGroup) michael@0: { michael@0: if (!IsNullOrHttp(targetURI) || !IsNullOrHttp(sourceURI)) { michael@0: return NS_OK; michael@0: } michael@0: michael@0: nsCOMPtr seer; michael@0: nsresult rv = EnsureGlobalSeer(getter_AddRefs(seer)); michael@0: NS_ENSURE_SUCCESS(rv, rv); michael@0: michael@0: nsCOMPtr loadContext; michael@0: michael@0: if (loadGroup) { michael@0: nsCOMPtr callbacks; michael@0: loadGroup->GetNotificationCallbacks(getter_AddRefs(callbacks)); michael@0: if (callbacks) { michael@0: loadContext = do_GetInterface(callbacks); michael@0: } michael@0: } michael@0: michael@0: return seer->Learn(targetURI, sourceURI, reason, loadContext); michael@0: } michael@0: michael@0: nsresult michael@0: SeerLearn(nsIURI *targetURI, nsIURI *sourceURI, SeerLearnReason reason, michael@0: nsIDocument *document) michael@0: { michael@0: if (!IsNullOrHttp(targetURI) || !IsNullOrHttp(sourceURI)) { michael@0: return NS_OK; michael@0: } michael@0: michael@0: nsCOMPtr seer; michael@0: nsresult rv = EnsureGlobalSeer(getter_AddRefs(seer)); michael@0: NS_ENSURE_SUCCESS(rv, rv); michael@0: michael@0: nsCOMPtr loadContext; michael@0: michael@0: if (document) { michael@0: loadContext = document->GetLoadContext(); michael@0: } michael@0: michael@0: return seer->Learn(targetURI, sourceURI, reason, loadContext); michael@0: } michael@0: michael@0: nsresult michael@0: SeerLearnRedirect(nsIURI *targetURI, nsIChannel *channel, michael@0: nsILoadContext *loadContext) michael@0: { michael@0: nsCOMPtr sourceURI; michael@0: nsresult rv = channel->GetOriginalURI(getter_AddRefs(sourceURI)); michael@0: NS_ENSURE_SUCCESS(rv, rv); michael@0: michael@0: bool sameUri; michael@0: rv = targetURI->Equals(sourceURI, &sameUri); michael@0: NS_ENSURE_SUCCESS(rv, rv); michael@0: michael@0: if (sameUri) { michael@0: return NS_OK; michael@0: } michael@0: michael@0: if (!IsNullOrHttp(targetURI) || !IsNullOrHttp(sourceURI)) { michael@0: return NS_OK; michael@0: } michael@0: michael@0: nsCOMPtr seer; michael@0: rv = EnsureGlobalSeer(getter_AddRefs(seer)); michael@0: NS_ENSURE_SUCCESS(rv, rv); michael@0: michael@0: return seer->Learn(targetURI, sourceURI, michael@0: nsINetworkSeer::LEARN_LOAD_REDIRECT, loadContext); michael@0: } michael@0: michael@0: } // ::mozilla::net michael@0: } // ::mozilla