Bug 1360480 - about:url-classifier: Cache information. r?francois draft
authorDimiL <dlee@mozilla.com>
Wed, 17 May 2017 10:32:33 +0800
changeset 582187 a40c8a825dfc61090a07f5c1b799f5d1d12703e9
parent 582183 74566d5345f4cab06c5683d4b620124104801e65
child 629691 a20eaae94bab67942ad7dd1edb837b90ff33cbc0
push id59995
push userbmo:dlee@mozilla.com
push dateMon, 22 May 2017 01:36:20 +0000
reviewersfrancois
bugs1360480
milestone55.0a1
Bug 1360480 - about:url-classifier: Cache information. r?francois MozReview-Commit-ID: 4YXtb2KPgwL
toolkit/components/url-classifier/Classifier.cpp
toolkit/components/url-classifier/Classifier.h
toolkit/components/url-classifier/LookupCache.cpp
toolkit/components/url-classifier/LookupCache.h
toolkit/components/url-classifier/moz.build
toolkit/components/url-classifier/nsIUrlClassifierDBService.idl
toolkit/components/url-classifier/nsIUrlClassifierInfo.idl
toolkit/components/url-classifier/nsUrlClassifierDBService.cpp
toolkit/components/url-classifier/nsUrlClassifierDBService.h
toolkit/components/url-classifier/nsUrlClassifierInfo.cpp
toolkit/components/url-classifier/nsUrlClassifierInfo.h
toolkit/components/url-classifier/nsUrlClassifierProxies.cpp
toolkit/components/url-classifier/nsUrlClassifierProxies.h
toolkit/components/url-classifier/tests/gtest/TestCaching.cpp
toolkit/content/aboutUrlClassifier.css
toolkit/content/aboutUrlClassifier.js
toolkit/content/aboutUrlClassifier.xhtml
toolkit/locales/en-US/chrome/global/aboutUrlClassifier.dtd
--- a/toolkit/components/url-classifier/Classifier.cpp
+++ b/toolkit/components/url-classifier/Classifier.cpp
@@ -882,16 +882,28 @@ Classifier::ApplyFullHashes(nsTArray<Tab
 
     aUpdates->ElementAt(i) = nullptr;
   }
 
   return NS_OK;
 }
 
 void
+Classifier::GetCacheInfo(const nsACString& aTable,
+                         nsIUrlClassifierCacheInfo** aCache)
+{
+  LookupCache* lookupCache = GetLookupCache(aTable);
+  if (!lookupCache) {
+    return;
+  }
+
+  lookupCache->GetCacheInfo(aCache);
+}
+
+void
 Classifier::DropStores()
 {
   for (uint32_t i = 0; i < mLookupCaches.Length(); i++) {
     delete mLookupCaches[i];
   }
   mLookupCaches.Clear();
 }
 
--- a/toolkit/components/url-classifier/Classifier.h
+++ b/toolkit/components/url-classifier/Classifier.h
@@ -119,16 +119,19 @@ public:
 
   // Swap in in-memory and on-disk database and remove all
   // update intermediaries.
   nsresult SwapInNewTablesAndCleanup();
 
   LookupCache *GetLookupCache(const nsACString& aTable,
                               bool aForUpdate = false);
 
+  void GetCacheInfo(const nsACString& aTable,
+                    nsIUrlClassifierCacheInfo** aCache);
+
 private:
   void DropStores();
   void DeleteTables(nsIFile* aDirectory, const nsTArray<nsCString>& aTables);
 
   nsresult CreateStoreDirectory();
   nsresult SetupPathNames();
   nsresult RecoverBackups();
   nsresult CleanToDelete();
--- a/toolkit/components/url-classifier/LookupCache.cpp
+++ b/toolkit/components/url-classifier/LookupCache.cpp
@@ -1,21 +1,23 @@
 //* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
 /* This Source Code Form is subject to the terms of the Mozilla Public
  * License, v. 2.0. If a copy of the MPL was not distributed with this
  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
 
 #include "LookupCache.h"
 #include "HashStore.h"
 #include "nsISeekableStream.h"
+#include "mozilla/ArrayUtils.h"
 #include "mozilla/Telemetry.h"
 #include "mozilla/Logging.h"
 #include "nsNetUtil.h"
 #include "prprf.h"
 #include "Classifier.h"
+#include "nsUrlClassifierInfo.h"
 
 // We act as the main entry point for all the real lookups,
 // so note that those are not done to the actual HashStore.
 // The latter solely exists to store the data needed to handle
 // the updates from the protocol.
 
 // This module provides a front for PrefixSet, mUpdateCompletions,
 // and mGetHashCache, which together contain everything needed to
@@ -42,16 +44,32 @@ extern mozilla::LazyLogModule gUrlClassi
 namespace mozilla {
 namespace safebrowsing {
 
 const int CacheResultV2::VER = CacheResult::V2;
 const int CacheResultV4::VER = CacheResult::V4;
 
 const int LookupCacheV2::VER = 2;
 
+static
+void CStringToHexString(const nsACString& aIn, nsACString& aOut)
+{
+  static const char* const lut = "0123456789ABCDEF";
+
+  size_t len = aIn.Length();
+  MOZ_ASSERT(len <= COMPLETE_SIZE);
+
+  aOut.SetCapacity(2 * len);
+  for (size_t i = 0; i < aIn.Length(); ++i) {
+    const char c = static_cast<const char>(aIn[i]);
+    aOut.Append(lut[(c >> 4) & 0x0F]);
+    aOut.Append(lut[c & 15]);
+  }
+}
+
 LookupCache::LookupCache(const nsACString& aTableName,
                          const nsACString& aProvider,
                          nsIFile* aRootStoreDir)
   : mPrimed(false)
   , mTableName(aTableName)
   , mProvider(aProvider)
   , mRootStoreDirectory(aRootStoreDir)
 {
@@ -208,16 +226,59 @@ LookupCache::ClearCache()
 void
 LookupCache::ClearAll()
 {
   ClearCache();
   ClearPrefixes();
   mPrimed = false;
 }
 
+void
+LookupCache::GetCacheInfo(nsIUrlClassifierCacheInfo** aCache)
+{
+  MOZ_ASSERT(aCache);
+
+  RefPtr<nsUrlClassifierCacheInfo> info = new nsUrlClassifierCacheInfo;
+  info->table = mTableName;
+
+  for (auto iter = mCache.ConstIter(); !iter.Done(); iter.Next()) {
+    RefPtr<nsUrlClassifierCacheEntry> entry = new nsUrlClassifierCacheEntry;
+
+    // Set prefix of the cache entry.
+    nsAutoCString prefix(reinterpret_cast<const char*>(&iter.Key()), PREFIX_SIZE);
+    CStringToHexString(prefix, entry->prefix);
+
+    // Set expiry of the cache entry.
+    CachedFullHashResponse* response = iter.Data();
+    entry->expirySec = response->negativeCacheExpirySec;
+
+    // Set positive cache.
+    FullHashExpiryCache& fullHashes = response->fullHashes;
+    for (auto iter2 = fullHashes.ConstIter(); !iter2.Done(); iter2.Next()) {
+      RefPtr<nsUrlClassifierPositiveCacheEntry> match =
+        new nsUrlClassifierPositiveCacheEntry;
+
+      // Set fullhash of positive cache entry.
+      CStringToHexString(iter2.Key(), match->fullhash);
+
+      // Set expiry of positive cache entry.
+      match->expirySec = iter2.Data();
+
+      entry->matches.AppendElement(
+        static_cast<nsIUrlClassifierPositiveCacheEntry*>(match));
+    }
+
+    info->entries.AppendElement(static_cast<nsIUrlClassifierCacheEntry*>(entry));
+  }
+
+  NS_ADDREF(*aCache = info);
+
+  return;
+}
+
 /* static */ bool
 LookupCache::IsCanonicalizedIP(const nsACString& aHost)
 {
   // The canonicalization process will have left IP addresses in dotted
   // decimal with no surprises.
   uint32_t i1, i2, i3, i4;
   char c;
   if (PR_sscanf(PromiseFlatCString(aHost).get(), "%u.%u.%u.%u%c",
@@ -421,31 +482,16 @@ LookupCache::LoadPrefixSet()
   }
 #endif
 
   return NS_OK;
 }
 
 #if defined(DEBUG)
 static
-void CStringToHexString(const nsACString& aIn, nsACString& aOut)
-{
-  static const char* const lut = "0123456789ABCDEF";
-  // 32 bytes is the longest hash
-  size_t len = COMPLETE_SIZE;
-
-  aOut.SetCapacity(2 * len);
-  for (size_t i = 0; i < aIn.Length(); ++i) {
-    const char c = static_cast<const char>(aIn[i]);
-    aOut.Append(lut[(c >> 4) & 0x0F]);
-    aOut.Append(lut[c & 15]);
-  }
-}
-
-static
 nsCString GetFormattedTimeString(int64_t aCurTimeSec)
 {
   PRExplodedTime pret;
   PR_ExplodeTime(aCurTimeSec * PR_USEC_PER_SEC, PR_GMTParameters, &pret);
 
   return nsPrintfCString(
          "%04d-%02d-%02d %02d:%02d:%02d UTC",
          pret.tm_year, pret.tm_month + 1, pret.tm_mday,
@@ -456,26 +502,29 @@ void
 LookupCache::DumpCache()
 {
   if (!LOG_ENABLED()) {
     return;
   }
 
   for (auto iter = mCache.ConstIter(); !iter.Done(); iter.Next()) {
     CachedFullHashResponse* response = iter.Data();
-    LOG(("Caches prefix: %X, Expire time: %s",
-         iter.Key(),
-         GetFormattedTimeString(response->negativeCacheExpirySec).get()));
+
+    nsAutoCString prefix;
+    CStringToHexString(
+      nsCString(reinterpret_cast<const char*>(&iter.Key()), PREFIX_SIZE), prefix);
+    LOG(("Cache prefix(%s): %s, Expiry: %s", mTableName.get(), prefix.get(),
+          GetFormattedTimeString(response->negativeCacheExpirySec).get()));
 
     FullHashExpiryCache& fullHashes = response->fullHashes;
     for (auto iter2 = fullHashes.ConstIter(); !iter2.Done(); iter2.Next()) {
-      nsAutoCString strFullhash;
-      CStringToHexString(iter2.Key(), strFullhash);
-      LOG(("  - %s, Expire time: %s", strFullhash.get(),
-           GetFormattedTimeString(iter2.Data()).get()));
+      nsAutoCString fullhash;
+      CStringToHexString(iter2.Key(), fullhash);
+      LOG(("  - %s, Expiry: %s", fullhash.get(),
+            GetFormattedTimeString(iter2.Data()).get()));
     }
   }
 }
 #endif
 
 nsresult
 LookupCacheV2::Init()
 {
@@ -596,28 +645,24 @@ LookupCacheV2::AddGethashResultToCache(A
     defaultExpirySec = aExpirySec;
   }
 
   for (const AddComplete& add : aAddCompletes) {
     nsDependentCSubstring fullhash(
       reinterpret_cast<const char*>(add.CompleteHash().buf), COMPLETE_SIZE);
 
     CachedFullHashResponse* response = mCache.LookupOrAdd(add.ToUint32());
-    // Set negative cache expiry to the same value as positive cache
-    // expiry when the gethash request returns a complete match.
-    if (response->negativeCacheExpirySec == 0) {
-      response->negativeCacheExpirySec = defaultExpirySec;
-    }
+    response->negativeCacheExpirySec = defaultExpirySec;
+
     FullHashExpiryCache& fullHashes = response->fullHashes;
     fullHashes.Put(fullhash, defaultExpirySec);
   }
 
   for (const Prefix& prefix : aMissPrefixes) {
     CachedFullHashResponse* response = mCache.LookupOrAdd(prefix.ToUint32());
-
     response->negativeCacheExpirySec = defaultExpirySec;
   }
 }
 
 nsresult
 LookupCacheV2::ReadCompletions()
 {
   HashStore store(mTableName, mProvider, mRootStoreDirectory);
--- a/toolkit/components/url-classifier/LookupCache.h
+++ b/toolkit/components/url-classifier/LookupCache.h
@@ -13,16 +13,17 @@
 #include "nsIFile.h"
 #include "nsIFileStreams.h"
 #include "mozilla/RefPtr.h"
 #include "nsUrlClassifierPrefixSet.h"
 #include "SBTelemetryUtils.h"
 #include "VariableLengthPrefixSet.h"
 #include "mozilla/Logging.h"
 #include "mozilla/TypedEnumBits.h"
+#include "nsIUrlClassifierInfo.h"
 
 namespace mozilla {
 namespace safebrowsing {
 
 #define MAX_HOST_COMPONENTS 5
 #define MAX_PATH_COMPONENTS 4
 
 class LookupResult {
@@ -204,16 +205,18 @@ public:
   // Check if completions can be found in cache.
   // Currently this is only used by testcase.
   bool IsInCache(uint32_t key) { return mCache.Get(key); };
 
 #if DEBUG
   void DumpCache();
 #endif
 
+  void GetCacheInfo(nsIUrlClassifierCacheInfo** aCache);
+
   virtual nsresult Open();
   virtual nsresult Init() = 0;
   virtual nsresult ClearPrefixes() = 0;
   virtual nsresult Has(const Completion& aCompletion,
                        bool* aHas,
                        uint32_t* aMatchLength,
                        bool* aConfirmed) = 0;
 
--- a/toolkit/components/url-classifier/moz.build
+++ b/toolkit/components/url-classifier/moz.build
@@ -7,16 +7,17 @@
 with Files('**'):
     BUG_COMPONENT = ('Toolkit', 'Safe Browsing')
 
 TEST_DIRS += ['tests']
 
 XPIDL_SOURCES += [
     'nsIUrlClassifierDBService.idl',
     'nsIUrlClassifierHashCompleter.idl',
+    'nsIUrlClassifierInfo.idl',
     'nsIUrlClassifierPrefixSet.idl',
     'nsIUrlClassifierStreamUpdater.idl',
     'nsIUrlClassifierUtils.idl',
     'nsIUrlListManager.idl',
 ]
 
 XPIDL_MODULE = 'url-classifier'
 
@@ -25,16 +26,17 @@ DEFINES['GOOGLE_PROTOBUF_NO_RTTI'] = Tru
 
 UNIFIED_SOURCES += [
     'ChunkSet.cpp',
     'Classifier.cpp',
     'LookupCache.cpp',
     'LookupCacheV4.cpp',
     'nsCheckSummedOutputStream.cpp',
     'nsUrlClassifierDBService.cpp',
+    'nsUrlClassifierInfo.cpp',
     'nsUrlClassifierProxies.cpp',
     'nsUrlClassifierUtils.cpp',
     'protobuf/safebrowsing.pb.cc',
     'ProtocolParser.cpp',
     'RiceDeltaDecoder.cpp',
     'SBTelemetryUtils.cpp',
 ]
 
--- a/toolkit/components/url-classifier/nsIUrlClassifierDBService.idl
+++ b/toolkit/components/url-classifier/nsIUrlClassifierDBService.idl
@@ -196,16 +196,21 @@ interface nsIUrlClassifierDBService : ns
   void resetDatabase();
 
   /**
    * Reload he url-classifier database. This will empty all cache for
    * completions from gethash, and reload it from database. Mostly intended
    * for use in tests.
    */
   void reloadDatabase();
+
+  /**
+   * Empty all the caches.
+   */
+  void clearCache();
 };
 
 /**
  * This is an internal helper interface for communication between the
  * main thread and the dbservice worker thread.  It is called for each
  * lookup to provide a set of possible results, which the main thread
  * may need to expand using an nsIUrlClassifierCompleter.
  */
new file mode 100644
--- /dev/null
+++ b/toolkit/components/url-classifier/nsIUrlClassifierInfo.idl
@@ -0,0 +1,73 @@
+ /* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+ /* This Source Code Form is subject to the terms of the Mozilla Public
+  * License, v. 2.0. If a copy of the MPL was not distributed with this
+  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "nsISupports.idl"
+#include "nsIArray.idl"
+
+/**
+ * nsIUrlClassifierPositiveCacheEntry Represents a positive cache entry.
+ */
+[scriptable, uuid(b3c27f8c-7db8-4f3f-97a5-5a94d781e565)]
+interface nsIUrlClassifierPositiveCacheEntry : nsISupports {
+  /**
+   * Fullhash for the positive cache entry.
+   */
+  readonly attribute ACString fullhash;
+
+  /**
+   * Positive cache expiry.
+   */
+  readonly attribute long long expiry;
+};
+
+/**
+ * nsIUrlClassifierCacheEntry contains cache information for
+ * a given prefix.
+ */
+[scriptable, uuid(d6297907-8236-4126-adaf-c3aa239a0d40)]
+interface nsIUrlClassifierCacheEntry : nsISupports {
+  /**
+   * Prefix for this cache entry.
+   */
+  readonly attribute ACString prefix;
+
+  /**
+   * Negative cache expiry.
+   */
+  readonly attribute long long expiry;
+
+  /**
+   * An array of nsIUrlClassifierPositiveCacheEntry, each item represents
+   * a positive cache entry with its fullhash and expiry.
+   */
+  readonly attribute nsIArray matches;
+};
+
+/**
+ * Cache information for a given table.
+ */
+[scriptable, function, uuid(69384f24-d9c5-4462-b24e-351c69e3b46a)]
+interface nsIUrlClassifierCacheInfo : nsISupports {
+  /**
+   * Table name.
+   */
+  readonly attribute ACString table;
+
+  /*
+   * An array of nsIUrlClassifierCacheEntry.
+   */
+  readonly attribute nsIArray entries;
+};
+
+/**
+ * Interface to query url-classifier information.
+ */
+[scriptable, function, uuid(411bbff4-1b88-4687-aa36-e2bbdd93f6e8)]
+interface nsIUrlClassifierInfo : nsISupports {
+  /**
+   * A synchronous call to return cache information for the table.
+   */
+  nsIUrlClassifierCacheInfo getCacheInfo(in ACString table);
+};
--- a/toolkit/components/url-classifier/nsUrlClassifierDBService.cpp
+++ b/toolkit/components/url-classifier/nsUrlClassifierDBService.cpp
@@ -730,16 +730,28 @@ nsUrlClassifierDBServiceWorker::ReloadDa
   // Create new mClassifier and load prefixset and completions from disk.
   rv = OpenDb();
   NS_ENSURE_SUCCESS(rv, rv);
 
   return NS_OK;
 }
 
 NS_IMETHODIMP
+nsUrlClassifierDBServiceWorker::ClearCache()
+{
+  nsTArray<nsCString> tables;
+  nsresult rv = mClassifier->ActiveTables(tables);
+  NS_ENSURE_SUCCESS(rv, rv);
+
+  mClassifier->ResetTables(Classifier::Clear_Cache, tables);
+
+  return NS_OK;
+}
+
+NS_IMETHODIMP
 nsUrlClassifierDBServiceWorker::CancelUpdate()
 {
   LOG(("nsUrlClassifierDBServiceWorker::CancelUpdate"));
 
   if (mUpdateObserver) {
     LOG(("UpdateObserver exists, cancelling"));
 
     mUpdateStatus = NS_BINDING_ABORTED;
@@ -939,16 +951,29 @@ nsUrlClassifierDBServiceWorker::ClearLas
 {
   MOZ_ASSERT(!NS_IsMainThread(), "Must be on the background thread");
   if (mLastResults) {
     mLastResults->Clear();
   }
   return NS_OK;
 }
 
+nsresult
+nsUrlClassifierDBServiceWorker::GetCacheInfo(const nsACString& aTable,
+                                             nsIUrlClassifierCacheInfo** aCache)
+{
+  MOZ_ASSERT(!NS_IsMainThread(), "Must be on the background thread");
+  if (!mClassifier) {
+    return NS_ERROR_NOT_AVAILABLE;
+  }
+
+  mClassifier->GetCacheInfo(aTable, aCache);
+  return NS_OK;
+}
+
 bool
 nsUrlClassifierDBServiceWorker::IsSameAsLastResults(CacheResultArray& aResult)
 {
   if (!mLastResults || mLastResults->Length() != aResult.Length()) {
     return false;
   }
 
   bool equal = true;
@@ -1502,16 +1527,17 @@ nsUrlClassifierClassifyCallback::HandleR
 // Proxy class implementation
 
 NS_IMPL_ADDREF(nsUrlClassifierDBService)
 NS_IMPL_RELEASE(nsUrlClassifierDBService)
 NS_INTERFACE_MAP_BEGIN(nsUrlClassifierDBService)
   // Only nsIURIClassifier is supported in the content process!
   NS_INTERFACE_MAP_ENTRY_CONDITIONAL(nsIUrlClassifierDBService, XRE_IsParentProcess())
   NS_INTERFACE_MAP_ENTRY(nsIURIClassifier)
+  NS_INTERFACE_MAP_ENTRY(nsIUrlClassifierInfo)
   NS_INTERFACE_MAP_ENTRY_CONDITIONAL(nsIObserver, XRE_IsParentProcess())
   NS_INTERFACE_MAP_ENTRY_AMBIGUOUS(nsISupports, nsIURIClassifier)
 NS_INTERFACE_MAP_END
 
 /* static */ nsUrlClassifierDBService*
 nsUrlClassifierDBService::GetInstance(nsresult *result)
 {
   *result = NS_OK;
@@ -2193,16 +2219,34 @@ nsUrlClassifierDBService::ReloadDatabase
   if (mWorker->IsBusyUpdating()) {
     LOG(("Failed to ReloadDatabase because of the unfinished update."));
     return NS_ERROR_FAILURE;
   }
 
   return mWorkerProxy->ReloadDatabase();
 }
 
+NS_IMETHODIMP
+nsUrlClassifierDBService::ClearCache()
+{
+  NS_ENSURE_TRUE(gDbBackgroundThread, NS_ERROR_NOT_INITIALIZED);
+
+  return mWorkerProxy->ClearCache();
+}
+
+
+NS_IMETHODIMP
+nsUrlClassifierDBService::GetCacheInfo(const nsACString& aTable,
+                                       nsIUrlClassifierCacheInfo** aCache)
+{
+  NS_ENSURE_TRUE(gDbBackgroundThread, NS_ERROR_NOT_INITIALIZED);
+
+  return mWorkerProxy->GetCacheInfo(aTable, aCache);
+}
+
 nsresult
 nsUrlClassifierDBService::CacheCompletions(CacheResultArray *results)
 {
   NS_ENSURE_TRUE(gDbBackgroundThread, NS_ERROR_NOT_INITIALIZED);
 
   return mWorkerProxy->CacheCompletions(results);
 }
 
--- a/toolkit/components/url-classifier/nsUrlClassifierDBService.h
+++ b/toolkit/components/url-classifier/nsUrlClassifierDBService.h
@@ -10,16 +10,17 @@
 
 #include "nsID.h"
 #include "nsInterfaceHashtable.h"
 #include "nsIObserver.h"
 #include "nsUrlClassifierPrefixSet.h"
 #include "nsIUrlClassifierHashCompleter.h"
 #include "nsIUrlListManager.h"
 #include "nsIUrlClassifierDBService.h"
+#include "nsIUrlClassifierInfo.h"
 #include "nsIURIClassifier.h"
 #include "nsToolkitCompsCID.h"
 #include "nsICryptoHMAC.h"
 #include "mozilla/Attributes.h"
 #include "mozilla/Mutex.h"
 #include "mozilla/TimeStamp.h"
 
 #include "Entries.h"
@@ -78,31 +79,33 @@ TablesToResponse(const nsACString& table
 
 } // namespace safebrowsing
 } // namespace mozilla
 
 // This is a proxy class that just creates a background thread and delagates
 // calls to the background thread.
 class nsUrlClassifierDBService final : public nsIUrlClassifierDBService,
                                        public nsIURIClassifier,
+                                       public nsIUrlClassifierInfo,
                                        public nsIObserver
 {
 public:
   // This is thread safe. It throws an exception if the thread is busy.
   nsUrlClassifierDBService();
 
   nsresult Init();
 
   static nsUrlClassifierDBService* GetInstance(nsresult *result);
 
   NS_DECLARE_STATIC_IID_ACCESSOR(NS_URLCLASSIFIERDBSERVICE_CID)
 
   NS_DECL_THREADSAFE_ISUPPORTS
   NS_DECL_NSIURLCLASSIFIERDBSERVICE
   NS_DECL_NSIURICLASSIFIER
+  NS_DECL_NSIURLCLASSIFIERINFO
   NS_DECL_NSIOBSERVER
 
   bool CanComplete(const nsACString &tableName);
   bool GetCompleter(const nsACString& tableName,
                     nsIUrlClassifierHashCompleter** completer);
   nsresult CacheCompletions(mozilla::safebrowsing::CacheResultArray *results);
 
   static nsIThread* BackgroundThread();
@@ -224,16 +227,20 @@ public:
   bool IsBusyUpdating() const { return !!mUpdateObserver; }
 
   // Delegate Classifier to disable async update. If there is an
   // ongoing update on the update thread, we will be blocked until
   // the background update is done and callback is fired.
   // Should be called on the worker thread.
   void FlushAndDisableAsyncUpdate();
 
+  // A synchronous call to get cache information for the given table.
+  // This is only used by about:url-classifier now.
+  nsresult GetCacheInfo(const nsACString& aTable,
+                        nsIUrlClassifierCacheInfo** aCache);
 private:
   // No subclassing
   ~nsUrlClassifierDBServiceWorker();
 
   // Disallow copy constructor
   nsUrlClassifierDBServiceWorker(nsUrlClassifierDBServiceWorker&);
 
   nsresult NotifyUpdateObserver(nsresult aUpdateStatus);
new file mode 100644
--- /dev/null
+++ b/toolkit/components/url-classifier/nsUrlClassifierInfo.cpp
@@ -0,0 +1,105 @@
+/* This Source Code Form is subject to the terms of the Mozilla
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "nsUrlClassifierInfo.h"
+
+NS_IMPL_ISUPPORTS(nsUrlClassifierPositiveCacheEntry,
+                  nsIUrlClassifierPositiveCacheEntry)
+
+nsUrlClassifierPositiveCacheEntry::nsUrlClassifierPositiveCacheEntry()
+{
+}
+
+NS_IMETHODIMP
+nsUrlClassifierPositiveCacheEntry::GetExpiry(int64_t* aExpiry)
+{
+  if (!aExpiry) {
+    return NS_ERROR_NULL_POINTER;
+  }
+
+  *aExpiry = expirySec;
+  return NS_OK;
+}
+
+NS_IMETHODIMP
+nsUrlClassifierPositiveCacheEntry::GetFullhash(nsACString& aFullHash)
+{
+  aFullHash = fullhash;
+  return NS_OK;
+}
+
+NS_IMPL_ISUPPORTS(nsUrlClassifierCacheEntry,
+                  nsIUrlClassifierCacheEntry)
+
+nsUrlClassifierCacheEntry::nsUrlClassifierCacheEntry()
+{
+}
+
+NS_IMETHODIMP
+nsUrlClassifierCacheEntry::GetPrefix(nsACString& aPrefix)
+{
+  aPrefix = prefix;
+  return NS_OK;
+}
+
+NS_IMETHODIMP
+nsUrlClassifierCacheEntry::GetExpiry(int64_t* aExpiry)
+{
+  if (!aExpiry) {
+    return NS_ERROR_NULL_POINTER;
+  }
+
+  *aExpiry = expirySec;
+  return NS_OK;
+}
+
+NS_IMETHODIMP
+nsUrlClassifierCacheEntry::GetMatches(nsIArray** aMatches)
+{
+  if (!aMatches) {
+    return NS_ERROR_NULL_POINTER;
+  }
+
+  nsCOMPtr<nsIMutableArray> array(do_CreateInstance(NS_ARRAY_CONTRACTID));
+
+  for (uint32_t i = 0;i < matches.Length(); i++) {
+    array->AppendElement(matches[i], false);
+  }
+
+  NS_ADDREF(*aMatches = array);
+
+  return NS_OK;
+}
+
+NS_IMPL_ISUPPORTS(nsUrlClassifierCacheInfo,
+                  nsIUrlClassifierCacheInfo)
+
+nsUrlClassifierCacheInfo::nsUrlClassifierCacheInfo()
+{
+}
+
+NS_IMETHODIMP
+nsUrlClassifierCacheInfo::GetTable(nsACString& aTable)
+{
+  aTable = table;
+  return NS_OK;
+}
+
+NS_IMETHODIMP
+nsUrlClassifierCacheInfo::GetEntries(nsIArray** aEntries)
+{
+  if (!aEntries) {
+    return NS_ERROR_NULL_POINTER;
+  }
+
+  nsCOMPtr<nsIMutableArray> array(do_CreateInstance(NS_ARRAY_CONTRACTID));
+
+  for (uint32_t i = 0;i < entries.Length(); i++) {
+    array->AppendElement(entries[i], false);
+  }
+
+  NS_ADDREF(*aEntries = array);
+
+  return NS_OK;
+}
new file mode 100644
--- /dev/null
+++ b/toolkit/components/url-classifier/nsUrlClassifierInfo.h
@@ -0,0 +1,64 @@
+/* This Source Code Form is subject to the terms of the Mozilla
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef nsUrlClassifierInfo_h_
+#define nsUrlClassifierInfo_h_
+
+#include "nsIUrlClassifierInfo.h"
+#include "nsString.h"
+
+class nsUrlClassifierPositiveCacheEntry final : public nsIUrlClassifierPositiveCacheEntry
+{
+public:
+  nsUrlClassifierPositiveCacheEntry();
+
+  NS_DECL_THREADSAFE_ISUPPORTS
+  NS_DECL_NSIURLCLASSIFIERPOSITIVECACHEENTRY
+
+private:
+  ~nsUrlClassifierPositiveCacheEntry() {}
+
+public:
+  nsCString fullhash;
+
+  int64_t expirySec;
+};
+
+class nsUrlClassifierCacheEntry final : public nsIUrlClassifierCacheEntry
+{
+public:
+  nsUrlClassifierCacheEntry();
+
+  NS_DECL_THREADSAFE_ISUPPORTS
+  NS_DECL_NSIURLCLASSIFIERCACHEENTRY
+
+private:
+  ~nsUrlClassifierCacheEntry() {}
+
+public:
+  nsCString prefix;
+
+  int64_t expirySec;
+
+  nsTArray<nsCOMPtr<nsIUrlClassifierPositiveCacheEntry>> matches;
+};
+
+class nsUrlClassifierCacheInfo final : public nsIUrlClassifierCacheInfo
+{
+public:
+  nsUrlClassifierCacheInfo();
+
+  NS_DECL_THREADSAFE_ISUPPORTS
+  NS_DECL_NSIURLCLASSIFIERCACHEINFO
+
+private:
+  ~nsUrlClassifierCacheInfo() {}
+
+public:
+  nsCString table;
+
+  nsTArray<nsCOMPtr<nsIUrlClassifierCacheEntry>> entries;
+};
+
+#endif // nsUrlClassifierInfo_h_
--- a/toolkit/components/url-classifier/nsUrlClassifierProxies.cpp
+++ b/toolkit/components/url-classifier/nsUrlClassifierProxies.cpp
@@ -174,16 +174,25 @@ NS_IMETHODIMP
 UrlClassifierDBServiceWorkerProxy::ReloadDatabase()
 {
   nsCOMPtr<nsIRunnable> r =
     NewRunnableMethod(mTarget,
                       &nsUrlClassifierDBServiceWorker::ReloadDatabase);
   return DispatchToWorkerThread(r);
 }
 
+NS_IMETHODIMP
+UrlClassifierDBServiceWorkerProxy::ClearCache()
+{
+  nsCOMPtr<nsIRunnable> r =
+    NewRunnableMethod(mTarget,
+                      &nsUrlClassifierDBServiceWorker::ClearCache);
+  return DispatchToWorkerThread(r);
+}
+
 nsresult
 UrlClassifierDBServiceWorkerProxy::OpenDb()
 {
   nsCOMPtr<nsIRunnable> r =
     NewRunnableMethod(mTarget,
                       &nsUrlClassifierDBServiceWorker::OpenDb);
   return DispatchToWorkerThread(r);
 }
@@ -219,16 +228,39 @@ UrlClassifierDBServiceWorkerProxy::Clear
 }
 
 NS_IMETHODIMP
 UrlClassifierDBServiceWorkerProxy::ClearLastResultsRunnable::Run()
 {
   return mTarget->ClearLastResults();
 }
 
+nsresult
+UrlClassifierDBServiceWorkerProxy::GetCacheInfo(const nsACString& aTable,
+                                                nsIUrlClassifierCacheInfo** aCache)
+{
+  nsCOMPtr<nsIRunnable> r = new GetCacheInfoRunnable(mTarget, aTable, aCache);
+
+  nsIThread* t = nsUrlClassifierDBService::BackgroundThread();
+  if (!t) {
+    return NS_ERROR_FAILURE;
+  }
+
+  // This blocks main thread but since 'GetCacheInfo' is only used by
+  // about:url-classifier so it should be fine.
+  mozilla::SyncRunnable::DispatchToThread(t, r);
+  return NS_OK;
+}
+
+NS_IMETHODIMP
+UrlClassifierDBServiceWorkerProxy::GetCacheInfoRunnable::Run()
+{
+  return mTarget->GetCacheInfo(mTable, mCache);
+}
+
 NS_IMPL_ISUPPORTS(UrlClassifierLookupCallbackProxy,
                   nsIUrlClassifierLookupCallback)
 
 NS_IMETHODIMP
 UrlClassifierLookupCallbackProxy::LookupComplete
   (LookupResultArray * aResults)
 {
   nsCOMPtr<nsIRunnable> r = new LookupCompleteRunnable(mTarget, aResults);
--- a/toolkit/components/url-classifier/nsUrlClassifierProxies.h
+++ b/toolkit/components/url-classifier/nsUrlClassifierProxies.h
@@ -162,26 +162,46 @@ public:
       : mTarget(aTarget)
     { }
 
     NS_DECL_NSIRUNNABLE
   private:
     RefPtr<nsUrlClassifierDBServiceWorker> mTarget;
   };
 
+  class GetCacheInfoRunnable: public mozilla::Runnable
+  {
+  public:
+    explicit GetCacheInfoRunnable(nsUrlClassifierDBServiceWorker* aTarget,
+                                  const nsACString& aTable,
+                                  nsIUrlClassifierCacheInfo** aCache)
+      : mTarget(aTarget),
+        mTable(aTable),
+        mCache(aCache)
+    { }
+
+    NS_DECL_NSIRUNNABLE
+  private:
+    RefPtr<nsUrlClassifierDBServiceWorker> mTarget;
+    nsCString mTable;
+    nsIUrlClassifierCacheInfo** mCache;
+  };
+
 public:
   nsresult DoLocalLookup(const nsACString& spec,
                          const nsACString& tables,
                          mozilla::safebrowsing::LookupResultArray* results);
 
   nsresult OpenDb();
   nsresult CloseDb();
 
   nsresult CacheCompletions(mozilla::safebrowsing::CacheResultArray * aEntries);
 
+  nsresult GetCacheInfo(const nsACString& aTable,
+                        nsIUrlClassifierCacheInfo** aCache);
 private:
   ~UrlClassifierDBServiceWorkerProxy() {}
 
   RefPtr<nsUrlClassifierDBServiceWorker> mTarget;
 };
 
 // The remaining classes here are all proxies to the main thread
 
--- a/toolkit/components/url-classifier/tests/gtest/TestCaching.cpp
+++ b/toolkit/components/url-classifier/tests/gtest/TestCaching.cpp
@@ -13,46 +13,51 @@ SetupCacheEntry(LookupCacheV2* aLookupCa
                 bool aNegExpired = false,
                 bool aPosExpired = false)
 {
   AddCompleteArray completes;
   AddCompleteArray emptyCompletes;
   MissPrefixArray misses;
   MissPrefixArray emptyMisses;
 
-  nsCOMPtr<nsICryptoHash> cryptoHash = do_CreateInstance(NS_CRYPTO_HASH_CONTRACTID);
+  nsCOMPtr<nsICryptoHash> cryptoHash =
+    do_CreateInstance(NS_CRYPTO_HASH_CONTRACTID);
 
   AddComplete* add = completes.AppendElement(fallible);
   add->complete.FromPlaintext(aCompletion, cryptoHash);
 
   Prefix* prefix = misses.AppendElement(fallible);
   prefix->FromPlaintext(aCompletion, cryptoHash);
 
+  // Setup positive cache first otherwise negative cache expiry will be
+  // overwritten.
+  int64_t posExpirySec = aPosExpired ? EXPIRED_TIME_SEC : NOTEXPIRED_TIME_SEC;
+  aLookupCache->AddGethashResultToCache(completes, emptyMisses, posExpirySec);
+
   int64_t negExpirySec = aNegExpired ? EXPIRED_TIME_SEC : NOTEXPIRED_TIME_SEC;
   aLookupCache->AddGethashResultToCache(emptyCompletes, misses, negExpirySec);
-
-  int64_t posExpirySec = aPosExpired ? EXPIRED_TIME_SEC : NOTEXPIRED_TIME_SEC;
-  aLookupCache->AddGethashResultToCache(completes, emptyMisses, posExpirySec);
 }
 
 static void
 SetupCacheEntry(LookupCacheV4* aLookupCache,
                 const nsCString& aCompletion,
                 bool aNegExpired = false,
                 bool aPosExpired = false)
 {
   FullHashResponseMap map;
 
   Prefix prefix;
-  nsCOMPtr<nsICryptoHash> cryptoHash = do_CreateInstance(NS_CRYPTO_HASH_CONTRACTID);
+  nsCOMPtr<nsICryptoHash> cryptoHash =
+    do_CreateInstance(NS_CRYPTO_HASH_CONTRACTID);
   prefix.FromPlaintext(aCompletion, cryptoHash);
 
   CachedFullHashResponse* response = map.LookupOrAdd(prefix.ToUint32());
 
-  response->negativeCacheExpirySec = aNegExpired ? EXPIRED_TIME_SEC : NOTEXPIRED_TIME_SEC;
+  response->negativeCacheExpirySec =
+    aNegExpired ? EXPIRED_TIME_SEC : NOTEXPIRED_TIME_SEC;
   response->fullHashes.Put(GeneratePrefix(aCompletion, COMPLETE_SIZE),
                            aPosExpired ? EXPIRED_TIME_SEC : NOTEXPIRED_TIME_SEC);
 
   aLookupCache->AddFullHashResponseToCache(map);
 }
 
 template<typename T>
 void
--- a/toolkit/content/aboutUrlClassifier.css
+++ b/toolkit/content/aboutUrlClassifier.css
@@ -53,17 +53,21 @@ td {
   text-align: start;
   border-color: var(--in-content-table-border-dark-color);
 }
 
 #provider-table > tbody > tr >  td:last-child {
   text-align: center;
 }
 
-#debug-table {
+#cache-table > tbody > tr >  td:last-child {
+  text-align: center;
+}
+
+#debug-table, #cache-table {
   margin-top: 20px;
 }
 
 .options > .toggle-container-with-text {
   display: inline-flex;
 }
 
 button {
--- a/toolkit/content/aboutUrlClassifier.js
+++ b/toolkit/content/aboutUrlClassifier.js
@@ -16,24 +16,26 @@ const UPDATE_FINISH = "safebrowsing-upda
 const JSLOG_PREF = "browser.safebrowsing.debug";
 
 const STR_NA = bundle.GetStringFromName("NotAvailable");
 
 function unLoad() {
   window.removeEventListener("unload", unLoad);
 
   Provider.uninit();
+  Cache.uninit();
   Debug.uninit();
 }
 
 function onLoad() {
   window.removeEventListener("load", onLoad);
   window.addEventListener("unload", unLoad);
 
   Provider.init();
+  Cache.init();
   Debug.init();
 }
 
 /*
  * Provider
  */
 var Provider = {
   providers: null,
@@ -175,16 +177,155 @@ var Provider = {
       let elem = document.getElementById(provider + "-col-lastupdateresult");
       elem.childNodes[0].nodeValue = bundle.GetStringFromName("CannotUpdate");
     }
   },
 
 };
 
 /*
+ * Cache
+ */
+var Cache = {
+  // Tables that show cahe entries.
+  showCacheEnties: null,
+
+  init() {
+    this.showCacheEnties = new Set();
+
+    this.register()
+    this.render();
+  },
+
+  uninit() {
+    Services.obs.removeObserver(this.refresh, UPDATE_FINISH);
+  },
+
+  register() {
+    this.refresh = this.refresh.bind(this);
+    Services.obs.addObserver(this.refresh, UPDATE_FINISH);
+  },
+
+  render() {
+    this.createCacheEntries();
+
+    let refreshBtn = document.getElementById("refresh-cache-btn");
+    refreshBtn.addEventListener("click", () => { this.refresh(); });
+
+    let clearBtn = document.getElementById("clear-cache-btn");
+    clearBtn.addEventListener("click", () => {
+      let dbservice = Cc["@mozilla.org/url-classifier/dbservice;1"]
+                      .getService(Ci.nsIUrlClassifierDBService);
+      dbservice.clearCache();
+      // Since clearCache is async call, we just simply assume it will be
+      // updated in 100 milli-seconds.
+      setTimeout(() => { this.refresh(); }, 100);
+    });
+  },
+
+  refresh() {
+    this.clearCacheEntries();
+    this.createCacheEntries();
+  },
+
+  clearCacheEntries() {
+    let ctbody = document.getElementById("cache-table-body");
+    while (ctbody.firstChild) {
+      ctbody.firstChild.remove();
+    }
+
+    let cetbody = document.getElementById("cache-entries-table-body");
+    while (cetbody.firstChild) {
+      cetbody.firstChild.remove();
+    }
+  },
+
+  createCacheEntries() {
+    function createRow(tds, body, cols) {
+      let tr = document.createElement("tr");
+      tds.forEach(function(v, i, a) {
+        let td = document.createElement("td");
+        if (i == 0 && tds.length != cols) {
+          td.setAttribute("colspan", cols - tds.length + 1);
+        }
+        let elem = typeof v === "object" ? v : document.createTextNode(v);
+        td.appendChild(elem);
+        tr.appendChild(td);
+      })
+      body.appendChild(tr);
+    }
+
+    let dbservice = Cc["@mozilla.org/url-classifier/dbservice;1"]
+                    .getService(Ci.nsIUrlClassifierInfo);
+
+    for (let provider of Provider.providers) {
+      let pref = "browser.safebrowsing.provider." + provider + ".lists";
+      let tables = Services.prefs.getCharPref(pref, "").split(",");
+
+      for (let table of tables) {
+        let cache = dbservice.getCacheInfo(table);
+        let entries = cache.entries;
+        if (entries.length === 0) {
+          this.showCacheEnties.delete(table);
+          continue;
+        }
+
+        let positiveCacheCount = 0;
+        for (let i = 0; i < entries.length ; i++) {
+          let entry = entries.queryElementAt(i, Ci.nsIUrlClassifierCacheEntry);
+          let matches = entry.matches;
+          positiveCacheCount += matches.length;
+
+          // If we don't have to show cache entries for this table then just
+          // skip the following code.
+          if (!this.showCacheEnties.has(table)) {
+            continue;
+          }
+
+          let tds = [table, entry.prefix, new Date(entry.expiry * 1000).toString()];
+          let j = 0;
+          do {
+            if (matches.length >= 1) {
+              let match =
+                matches.queryElementAt(j, Ci.nsIUrlClassifierPositiveCacheEntry);
+              let list = [match.fullhash, new Date(match.expiry * 1000).toString()];
+              tds = tds.concat(list);
+            } else {
+              tds = tds.concat([STR_NA, STR_NA])
+            }
+            createRow(tds, document.getElementById("cache-entries-table-body"), 5);
+            j++;
+            tds = [""];
+          } while (j < matches.length)
+        }
+
+        // Create cache information entries.
+        let chk = document.createElement("input");
+        chk.type = "checkbox";
+        chk.checked = this.showCacheEnties.has(table);
+        chk.addEventListener("click", () => {
+          if (chk.checked) {
+            this.showCacheEnties.add(table);
+          } else {
+            this.showCacheEnties.delete(table);
+          }
+          this.refresh();
+        });
+
+        let tds = [table, entries.length, positiveCacheCount, chk];
+        createRow(tds, document.getElementById("cache-table-body"), tds.length);
+      }
+    }
+
+    let entries_div = document.getElementById("cache-entries");
+    entries_div.style.display = this.showCacheEnties.size == 0 ? "none" : "block";
+  },
+};
+
+/*
  * Debug
  */
 var Debug = {
   // url-classifier NSPR Log modules.
   modules: ["UrlClassifierDbService",
             "nsChannelClassifier",
             "UrlClassifierProtocolParser",
             "UrlClassifierStreamUpdater",
--- a/toolkit/content/aboutUrlClassifier.xhtml
+++ b/toolkit/content/aboutUrlClassifier.xhtml
@@ -32,16 +32,55 @@
           <th id="col-update">&aboutUrlClassifier.providerUpdateBtn;</th>
         </tr>
       </thead>
       <tbody id="provider-table-body">
         <!-- data is generated in javascript -->
       </tbody>
     </table>
   </div>
+  <div id="cache">
+    <h2 class="major-section">&aboutUrlClassifier.cacheTitle;</h2>
+    <div id="cache-modules" class="options">
+      <button id="refresh-cache-btn">&aboutUrlClassifier.cacheRefreshBtn;</button>
+      <button id="clear-cache-btn">&aboutUrlClassifier.cacheClearBtn;</button>
+      <br></br>
+    </div>
+    <table id="cache-table">
+      <thead>
+        <tr id="cache-head-row">
+          <th id="col-tablename">&aboutUrlClassifier.cacheTableName;</th>
+          <th id="col-negativeentries">&aboutUrlClassifier.cacheNCacheEntries;</th>
+          <th id="col-positiveentries">&aboutUrlClassifier.cachePCacheEntries;</th>
+          <th id="col-showentries">&aboutUrlClassifier.cacheShowEntries;</th>
+        </tr>
+      </thead>
+      <tbody id="cache-table-body">
+        <!-- data is generated in javascript -->
+      </tbody>
+    </table>
+    <br></br>
+  </div>
+  <div id="cache-entries">
+    <h2 class="major-section">&aboutUrlClassifier.cacheEntries;</h2>
+    <table id="cache-entries-table">
+      <thead>
+        <tr id="cache-entries-row">
+          <th id="col-table">&aboutUrlClassifier.cacheTableName;</th>
+          <th id="col-prefix">&aboutUrlClassifier.cachePrefix;</th>
+          <th id="col-n-expire">&aboutUrlClassifier.cacheNCacheExpiry;</th>
+          <th id="col-fullhash">&aboutUrlClassifier.cacheFullhash;</th>
+          <th id="col-p-expire">&aboutUrlClassifier.cachePCacheExpiry;</th>
+        </tr>
+      </thead>
+      <tbody id="cache-entries-table-body">
+        <!-- data is generated in javascript -->
+      </tbody>
+    </table>
+  </div>
   <div id="debug">
     <h2 class="major-section">&aboutUrlClassifier.debugTitle;</h2>
     <div id="debug-modules" class="options">
       <input id="log-modules" type="text" value=""/>
       <button id="set-log-modules">&aboutUrlClassifier.debugModuleBtn;</button>
       <br></br>
       <input id="log-file" type="text" value=""/>
       <button id="set-log-file">&aboutUrlClassifier.debugFileBtn;</button>
--- a/toolkit/locales/en-US/chrome/global/aboutUrlClassifier.dtd
+++ b/toolkit/locales/en-US/chrome/global/aboutUrlClassifier.dtd
@@ -5,16 +5,28 @@
 <!-- LOCALIZATION NOTE the term "url-classifier" should not be translated. -->
 <!ENTITY aboutUrlClassifier.pageTitle                   "Information about the url-classifier">
 <!ENTITY aboutUrlClassifier.providerTitle               "Provider">
 <!ENTITY aboutUrlClassifier.provider                    "Provider">
 <!ENTITY aboutUrlClassifier.providerLastUpdateTime      "Last update time">
 <!ENTITY aboutUrlClassifier.providerNextUpdateTime      "Next update time">
 <!ENTITY aboutUrlClassifier.providerLastUpdateStatus    "Last update status">
 <!ENTITY aboutUrlClassifier.providerUpdateBtn           "Update">
+<!ENTITY aboutUrlClassifier.cacheTitle                  "Cache">
+<!ENTITY aboutUrlClassifier.cacheRefreshBtn             "Refresh">
+<!ENTITY aboutUrlClassifier.cacheClearBtn               "Clear">
+<!ENTITY aboutUrlClassifier.cacheTableName              "Table name">
+<!ENTITY aboutUrlClassifier.cacheNCacheEntries          "Number of negative cache entries">
+<!ENTITY aboutUrlClassifier.cachePCacheEntries          "Number of positive cache entries">
+<!ENTITY aboutUrlClassifier.cacheShowEntries            "Show entries">
+<!ENTITY aboutUrlClassifier.cacheEntries                "Cache Entries">
+<!ENTITY aboutUrlClassifier.cachePrefix                 "Prefix">
+<!ENTITY aboutUrlClassifier.cacheNCacheExpiry           "Negative cache expiry">
+<!ENTITY aboutUrlClassifier.cacheFullhash               "Full hash">
+<!ENTITY aboutUrlClassifier.cachePCacheExpiry           "Positive cache expiry">
 <!ENTITY aboutUrlClassifier.lookupTitle                 "Lookup">
 <!ENTITY aboutUrlClassifier.lookupUrl                   "Url">
 <!ENTITY aboutUrlClassifier.lookupMatch                 "Match">
 <!ENTITY aboutUrlClassifier.lookupMatchBtn              "Check Match Result">
 <!ENTITY aboutUrlClassifier.lookupLookup                "Lookup">
 <!ENTITY aboutUrlClassifier.lookupBtn                   "Check Lookup Result">
 <!ENTITY aboutUrlClassifier.cacheTitle                  "Cache">
 <!ENTITY aboutUrlClassifier.memoryTitle                 "Memory">