Bug 1515286 - Introduce nsIURIClassifier.getFeatureByName() and nsIURIClassifier.createFeatureWithTables(), r=dimi
authorAndrea Marchesini <amarchesini@mozilla.com>
Sat, 05 Jan 2019 09:10:45 +0100
changeset 509735 cf02de018ec52940d002efde37c539dec74a7469
parent 509734 df5c4c4e6a7a6b29a61acdc18184c6a7708ba369
child 509736 4e04d5fd29aa51a57662ff65fa64c9ed6e504b11
push id10547
push userffxbld-merge
push dateMon, 21 Jan 2019 13:03:58 +0000
treeherdermozilla-beta@24ec1916bffe [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersdimi
bugs1515286
milestone66.0a1
first release with
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
last release without
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
Bug 1515286 - Introduce nsIURIClassifier.getFeatureByName() and nsIURIClassifier.createFeatureWithTables(), r=dimi
browser/base/content/browser-contentblocking.js
netwerk/url-classifier/UrlClassifierFeatureCustomTables.cpp
netwerk/url-classifier/UrlClassifierFeatureCustomTables.h
netwerk/url-classifier/UrlClassifierFeatureFactory.cpp
netwerk/url-classifier/UrlClassifierFeatureFactory.h
netwerk/url-classifier/UrlClassifierFeatureFlash.cpp
netwerk/url-classifier/UrlClassifierFeatureFlash.h
netwerk/url-classifier/UrlClassifierFeatureLoginReputation.cpp
netwerk/url-classifier/UrlClassifierFeatureLoginReputation.h
netwerk/url-classifier/UrlClassifierFeatureTrackingAnnotation.cpp
netwerk/url-classifier/UrlClassifierFeatureTrackingAnnotation.h
netwerk/url-classifier/UrlClassifierFeatureTrackingProtection.cpp
netwerk/url-classifier/UrlClassifierFeatureTrackingProtection.h
netwerk/url-classifier/moz.build
netwerk/url-classifier/nsIURIClassifier.idl
testing/specialpowers/content/specialpowersAPI.js
toolkit/components/url-classifier/UrlClassifierTelemetryUtils.h
toolkit/components/url-classifier/nsUrlClassifierDBService.cpp
toolkit/components/url-classifier/tests/mochitest/test_classifier.html
toolkit/components/url-classifier/tests/unit/test_features.js
toolkit/components/url-classifier/tests/unit/xpcshell.ini
--- a/browser/base/content/browser-contentblocking.js
+++ b/browser/base/content/browser-contentblocking.js
@@ -149,19 +149,26 @@ var TrackingProtection = {
   },
 
   // Given a URI from a source that was tracking-annotated, figure out
   // if it's really on the tracking table or just on the annotation table.
   _isOnTrackingTable(uri) {
     if (this.trackingTable == this.trackingAnnotationTable) {
       return true;
     }
+
+    let feature = classifierService.getFeatureByName("tracking-protection");
+    if (!feature) {
+      return false;
+    }
+
     return new Promise(resolve => {
-      classifierService.asyncClassifyLocalWithTables(uri, this.trackingTable, [], [],
-        (code, list) => resolve(!!list));
+      classifierService.asyncClassifyLocalWithFeatures(uri, [feature],
+        Ci.nsIUrlClassifierFeature.blacklist,
+        list => resolve(!!list.length));
     });
   },
 
   async _createListItem(origin, actions) {
     // Figure out if this list entry was actually detected by TP or something else.
     let isDetected = false;
     let isAllowed = false;
     for (let [state] of actions) {
new file mode 100644
--- /dev/null
+++ b/netwerk/url-classifier/UrlClassifierFeatureCustomTables.cpp
@@ -0,0 +1,103 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* 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 "UrlClassifierFeatureCustomTables.h"
+
+namespace mozilla {
+
+NS_INTERFACE_MAP_BEGIN(UrlClassifierFeatureCustomTables)
+  NS_INTERFACE_MAP_ENTRY_AMBIGUOUS(nsISupports, nsIUrlClassifierFeature)
+  NS_INTERFACE_MAP_ENTRY(nsIUrlClassifierFeature)
+NS_INTERFACE_MAP_END
+
+NS_IMPL_ADDREF(UrlClassifierFeatureCustomTables)
+NS_IMPL_RELEASE(UrlClassifierFeatureCustomTables)
+
+UrlClassifierFeatureCustomTables::UrlClassifierFeatureCustomTables(
+    const nsACString& aName, const nsTArray<nsCString>& aBlacklistTables,
+    const nsTArray<nsCString>& aWhitelistTables)
+    : mName(aName),
+      mBlacklistTables(aBlacklistTables),
+      mWhitelistTables(aWhitelistTables) {}
+
+UrlClassifierFeatureCustomTables::~UrlClassifierFeatureCustomTables() = default;
+
+NS_IMETHODIMP
+UrlClassifierFeatureCustomTables::GetName(nsACString& aName) {
+  aName = mName;
+  return NS_OK;
+}
+
+NS_IMETHODIMP
+UrlClassifierFeatureCustomTables::GetTables(
+    nsIUrlClassifierFeature::listType aListType, nsTArray<nsCString>& aTables) {
+  if (aListType == nsIUrlClassifierFeature::blacklist) {
+    aTables = mBlacklistTables;
+    return NS_OK;
+  }
+
+  if (aListType == nsIUrlClassifierFeature::whitelist) {
+    aTables = mWhitelistTables;
+    return NS_OK;
+  }
+
+  return NS_ERROR_FAILURE;
+}
+
+NS_IMETHODIMP
+UrlClassifierFeatureCustomTables::HasTable(
+    const nsACString& aTable, nsIUrlClassifierFeature::listType aListType,
+    bool* aResult) {
+  NS_ENSURE_ARG_POINTER(aResult);
+
+  if (aListType == nsIUrlClassifierFeature::blacklist) {
+    *aResult = mBlacklistTables.Contains(aTable);
+    return NS_OK;
+  }
+
+  if (aListType == nsIUrlClassifierFeature::whitelist) {
+    *aResult = mWhitelistTables.Contains(aTable);
+    return NS_OK;
+  }
+
+  return NS_ERROR_FAILURE;
+}
+
+NS_IMETHODIMP
+UrlClassifierFeatureCustomTables::HasHostInPreferences(
+    const nsACString& aHost, nsIUrlClassifierFeature::listType aListType,
+    nsACString& aPrefTableName, bool* aResult) {
+  NS_ENSURE_ARG_POINTER(aResult);
+  *aResult = false;
+  return NS_OK;
+}
+
+NS_IMETHODIMP
+UrlClassifierFeatureCustomTables::GetSkipHostList(nsACString& aList) {
+  return NS_OK;
+}
+
+NS_IMETHODIMP
+UrlClassifierFeatureCustomTables::ProcessChannel(nsIChannel* aChannel,
+                                                 const nsACString& aList,
+                                                 bool* aShouldContinue) {
+  NS_ENSURE_ARG_POINTER(aChannel);
+  NS_ENSURE_ARG_POINTER(aShouldContinue);
+
+  // This is not a blocking feature.
+  *aShouldContinue = true;
+
+  return NS_OK;
+}
+
+NS_IMETHODIMP
+UrlClassifierFeatureCustomTables::GetURIByListType(
+    nsIChannel* aChannel, nsIUrlClassifierFeature::listType aListType,
+    nsIURI** aURI) {
+  return NS_ERROR_NOT_IMPLEMENTED;
+}
+
+}  // namespace mozilla
new file mode 100644
--- /dev/null
+++ b/netwerk/url-classifier/UrlClassifierFeatureCustomTables.h
@@ -0,0 +1,35 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* 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/. */
+
+#ifndef mozilla_UrlClassifierFeatureCustomTables_h
+#define mozilla_UrlClassifierFeatureCustomTables_h
+
+#include "nsIUrlClassifierFeature.h"
+#include "nsTArray.h"
+#include "nsString.h"
+
+namespace mozilla {
+
+class UrlClassifierFeatureCustomTables : public nsIUrlClassifierFeature {
+ public:
+  NS_DECL_ISUPPORTS
+  NS_DECL_NSIURLCLASSIFIERFEATURE
+
+  explicit UrlClassifierFeatureCustomTables(
+      const nsACString& aName, const nsTArray<nsCString>& aBlacklistTables,
+      const nsTArray<nsCString>& aWhitelistTables);
+
+ private:
+  virtual ~UrlClassifierFeatureCustomTables();
+
+  nsCString mName;
+  nsTArray<nsCString> mBlacklistTables;
+  nsTArray<nsCString> mWhitelistTables;
+};
+
+}  // namespace mozilla
+
+#endif  // mozilla_UrlClassifierFeatureCustomTables_h
--- a/netwerk/url-classifier/UrlClassifierFeatureFactory.cpp
+++ b/netwerk/url-classifier/UrlClassifierFeatureFactory.cpp
@@ -6,16 +6,17 @@
 
 #include "mozilla/net/UrlClassifierFeatureFactory.h"
 
 // List of Features
 #include "UrlClassifierFeatureFlash.h"
 #include "UrlClassifierFeatureLoginReputation.h"
 #include "UrlClassifierFeatureTrackingProtection.h"
 #include "UrlClassifierFeatureTrackingAnnotation.h"
+#include "UrlClassifierFeatureCustomTables.h"
 
 #include "nsAppRunner.h"
 
 namespace mozilla {
 namespace net {
 
 /* static */ void UrlClassifierFeatureFactory::Initialize() {
   // We want to expose Features only in the parent process.
@@ -72,10 +73,51 @@ namespace net {
 }
 
 /* static */
 nsIUrlClassifierFeature*
 UrlClassifierFeatureFactory::GetFeatureLoginReputation() {
   return UrlClassifierFeatureLoginReputation::MaybeGetOrCreate();
 }
 
+/* static */ already_AddRefed<nsIUrlClassifierFeature>
+UrlClassifierFeatureFactory::GetFeatureByName(const nsACString& aName) {
+  nsCOMPtr<nsIUrlClassifierFeature> feature;
+
+  // Tracking Protection
+  feature = UrlClassifierFeatureTrackingProtection::GetIfNameMatches(aName);
+  if (feature) {
+    return feature.forget();
+  }
+
+  // Tracking Annotation
+  feature = UrlClassifierFeatureTrackingAnnotation::GetIfNameMatches(aName);
+  if (feature) {
+    return feature.forget();
+  }
+
+  // Login reputation
+  feature = UrlClassifierFeatureLoginReputation::GetIfNameMatches(aName);
+  if (feature) {
+    return feature.forget();
+  }
+
+  // We use Flash feature just for document loading.
+  feature = UrlClassifierFeatureFlash::GetIfNameMatches(aName);
+  if (feature) {
+    return feature.forget();
+  }
+
+  return nullptr;
+}
+
+/* static */ already_AddRefed<nsIUrlClassifierFeature>
+UrlClassifierFeatureFactory::CreateFeatureWithTables(
+    const nsACString& aName, const nsTArray<nsCString>& aBlacklistTables,
+    const nsTArray<nsCString>& aWhitelistTables) {
+  nsCOMPtr<nsIUrlClassifierFeature> feature =
+      new UrlClassifierFeatureCustomTables(aName, aBlacklistTables,
+                                           aWhitelistTables);
+  return feature.forget();
+}
+
 }  // namespace net
 }  // namespace mozilla
--- a/netwerk/url-classifier/UrlClassifierFeatureFactory.h
+++ b/netwerk/url-classifier/UrlClassifierFeatureFactory.h
@@ -22,14 +22,21 @@ class UrlClassifierFeatureFactory final 
 
   static void Shutdown();
 
   static void GetFeaturesFromChannel(
       nsIChannel* aChannel,
       nsTArray<nsCOMPtr<nsIUrlClassifierFeature>>& aFeatures);
 
   static nsIUrlClassifierFeature* GetFeatureLoginReputation();
+
+  static already_AddRefed<nsIUrlClassifierFeature> GetFeatureByName(
+      const nsACString& aFeatureName);
+
+  static already_AddRefed<nsIUrlClassifierFeature> CreateFeatureWithTables(
+      const nsACString& aName, const nsTArray<nsCString>& aBlacklistTables,
+      const nsTArray<nsCString>& aWhitelistTables);
 };
 
 }  // namespace net
 }  // namespace mozilla
 
 #endif  // mozilla_net_UrlClassifierFeatureFactory_h
--- a/netwerk/url-classifier/UrlClassifierFeatureFlash.cpp
+++ b/netwerk/url-classifier/UrlClassifierFeatureFlash.cpp
@@ -106,16 +106,32 @@ UrlClassifierFeatureFlash::UrlClassifier
     MOZ_ASSERT(sFlashFeaturesMap[i].mFeature);
     if (!sFlashFeaturesMap[i].mSubdocumentOnly ||
         contentPolicyType == nsIContentPolicy::TYPE_SUBDOCUMENT) {
       aFeatures.AppendElement(sFlashFeaturesMap[i].mFeature);
     }
   }
 }
 
+/* static */ already_AddRefed<nsIUrlClassifierFeature>
+UrlClassifierFeatureFlash::GetIfNameMatches(const nsACString& aName) {
+  uint32_t numFeatures =
+      (sizeof(sFlashFeaturesMap) / sizeof(sFlashFeaturesMap[0]));
+  for (uint32_t i = 0; i < numFeatures; ++i) {
+    MOZ_ASSERT(sFlashFeaturesMap[i].mFeature);
+    if (aName.Equals(sFlashFeaturesMap[i].mName)) {
+      nsCOMPtr<nsIUrlClassifierFeature> self =
+          sFlashFeaturesMap[i].mFeature.get();
+      return self.forget();
+    }
+  }
+
+  return nullptr;
+}
+
 NS_IMETHODIMP
 UrlClassifierFeatureFlash::ProcessChannel(nsIChannel* aChannel,
                                           const nsACString& aList,
                                           bool* aShouldContinue) {
   NS_ENSURE_ARG_POINTER(aChannel);
   NS_ENSURE_ARG_POINTER(aShouldContinue);
 
   // This is not a blocking feature.
--- a/netwerk/url-classifier/UrlClassifierFeatureFlash.h
+++ b/netwerk/url-classifier/UrlClassifierFeatureFlash.h
@@ -16,16 +16,19 @@ class UrlClassifierFeatureFlash final : 
  public:
   static void Initialize();
   static void Shutdown();
 
   static void MaybeCreate(
       nsIChannel* aChannel,
       nsTArray<nsCOMPtr<nsIUrlClassifierFeature>>& aFeatures);
 
+  static already_AddRefed<nsIUrlClassifierFeature> GetIfNameMatches(
+      const nsACString& aName);
+
   NS_IMETHOD
   ProcessChannel(nsIChannel* aChannel, const nsACString& aList,
                  bool* aShouldContinue) override;
 
   NS_IMETHOD GetURIByListType(nsIChannel* aChannel,
                               nsIUrlClassifierFeature::listType aListType,
                               nsIURI** aURI) override;
 
--- a/netwerk/url-classifier/UrlClassifierFeatureLoginReputation.cpp
+++ b/netwerk/url-classifier/UrlClassifierFeatureLoginReputation.cpp
@@ -8,31 +8,34 @@
 
 #include "mozilla/StaticPrefs.h"
 
 namespace mozilla {
 namespace net {
 
 namespace {
 
+#define LOGIN_REPUTATION_FEATURE_NAME "login-reputation"
+
 #define PREF_PASSWORD_ALLOW_TABLE "urlclassifier.passwordAllowTable"
 
 StaticRefPtr<UrlClassifierFeatureLoginReputation> gFeatureLoginReputation;
 
 }  // namespace
 
 UrlClassifierFeatureLoginReputation::UrlClassifierFeatureLoginReputation()
-    : UrlClassifierFeatureBase(NS_LITERAL_CSTRING("login-reputation"),
-                               EmptyCString(),  // blacklist tables
-                               NS_LITERAL_CSTRING(PREF_PASSWORD_ALLOW_TABLE),
-                               EmptyCString(),  // blacklist pref
-                               EmptyCString(),  // whitelist pref
-                               EmptyCString(),  // blacklist pref table name
-                               EmptyCString(),  // whitelist pref table name
-                               EmptyCString())  // skip host pref
+    : UrlClassifierFeatureBase(
+          NS_LITERAL_CSTRING(LOGIN_REPUTATION_FEATURE_NAME),
+          EmptyCString(),  // blacklist tables
+          NS_LITERAL_CSTRING(PREF_PASSWORD_ALLOW_TABLE),
+          EmptyCString(),  // blacklist pref
+          EmptyCString(),  // whitelist pref
+          EmptyCString(),  // blacklist pref table name
+          EmptyCString(),  // whitelist pref table name
+          EmptyCString())  // skip host pref
 {}
 
 /* static */ void UrlClassifierFeatureLoginReputation::MaybeShutdown() {
   UC_LOG(("UrlClassifierFeatureLoginReputation: MaybeShutdown"));
 
   if (gFeatureLoginReputation) {
     gFeatureLoginReputation->ShutdownPreferences();
     gFeatureLoginReputation = nullptr;
@@ -48,16 +51,26 @@ UrlClassifierFeatureLoginReputation::May
   if (!gFeatureLoginReputation) {
     gFeatureLoginReputation = new UrlClassifierFeatureLoginReputation();
     gFeatureLoginReputation->InitializePreferences();
   }
 
   return gFeatureLoginReputation;
 }
 
+/* static */ already_AddRefed<nsIUrlClassifierFeature>
+UrlClassifierFeatureLoginReputation::GetIfNameMatches(const nsACString& aName) {
+  if (!aName.EqualsLiteral(LOGIN_REPUTATION_FEATURE_NAME)) {
+    return nullptr;
+  }
+
+  nsCOMPtr<nsIUrlClassifierFeature> self = MaybeGetOrCreate();
+  return self.forget();
+}
+
 NS_IMETHODIMP
 UrlClassifierFeatureLoginReputation::ProcessChannel(nsIChannel* aChannel,
                                                     const nsACString& aList,
                                                     bool* aShouldContinue) {
   MOZ_CRASH(
       "UrlClassifierFeatureLoginReputation::ProcessChannel should never be "
       "called");
   return NS_OK;
--- a/netwerk/url-classifier/UrlClassifierFeatureLoginReputation.h
+++ b/netwerk/url-classifier/UrlClassifierFeatureLoginReputation.h
@@ -16,16 +16,19 @@ namespace net {
 
 class UrlClassifierFeatureLoginReputation final
     : public UrlClassifierFeatureBase {
  public:
   static void MaybeShutdown();
 
   static nsIUrlClassifierFeature* MaybeGetOrCreate();
 
+  static already_AddRefed<nsIUrlClassifierFeature> GetIfNameMatches(
+      const nsACString& aName);
+
   NS_IMETHOD
   GetTables(nsIUrlClassifierFeature::listType aListType,
             nsTArray<nsCString>& aResult) override;
 
   NS_IMETHOD
   HasTable(const nsACString& aTable,
            nsIUrlClassifierFeature::listType aListType, bool* aResult) override;
 
--- a/netwerk/url-classifier/UrlClassifierFeatureTrackingAnnotation.cpp
+++ b/netwerk/url-classifier/UrlClassifierFeatureTrackingAnnotation.cpp
@@ -16,16 +16,18 @@
 #include "nsQueryObject.h"
 #include "TrackingDummyChannel.h"
 
 namespace mozilla {
 namespace net {
 
 namespace {
 
+#define TRACKING_ANNOTATION_FEATURE_NAME "tracking-annotation"
+
 #define URLCLASSIFIER_ANNOTATION_BLACKLIST \
   "urlclassifier.trackingAnnotationTable"
 #define URLCLASSIFIER_ANNOTATION_BLACKLIST_TEST_ENTRIES \
   "urlclassifier.trackingAnnotationTable.testEntries"
 #define URLCLASSIFIER_ANNOTATION_WHITELIST \
   "urlclassifier.trackingAnnotationWhitelistTable"
 #define URLCLASSIFIER_ANNOTATION_WHITELIST_TEST_ENTRIES \
   "urlclassifier.trackingAnnotationWhitelistTable.testEntries"
@@ -104,17 +106,17 @@ static void LowerPriorityHelper(nsIChann
     }
   }
 }
 
 }  // namespace
 
 UrlClassifierFeatureTrackingAnnotation::UrlClassifierFeatureTrackingAnnotation()
     : UrlClassifierFeatureBase(
-          NS_LITERAL_CSTRING("tracking-annotation"),
+          NS_LITERAL_CSTRING(TRACKING_ANNOTATION_FEATURE_NAME),
           NS_LITERAL_CSTRING(URLCLASSIFIER_ANNOTATION_BLACKLIST),
           NS_LITERAL_CSTRING(URLCLASSIFIER_ANNOTATION_WHITELIST),
           NS_LITERAL_CSTRING(URLCLASSIFIER_ANNOTATION_BLACKLIST_TEST_ENTRIES),
           NS_LITERAL_CSTRING(URLCLASSIFIER_ANNOTATION_WHITELIST_TEST_ENTRIES),
           NS_LITERAL_CSTRING(TABLE_ANNOTATION_BLACKLIST_PREF),
           NS_LITERAL_CSTRING(TABLE_ANNOTATION_WHITELIST_PREF),
           NS_LITERAL_CSTRING(URLCLASSIFIER_TRACKING_ANNOTATION_SKIP_URLS)) {}
 
@@ -151,16 +153,30 @@ UrlClassifierFeatureTrackingAnnotation::
     return nullptr;
   }
 
   RefPtr<UrlClassifierFeatureTrackingAnnotation> self =
       gFeatureTrackingAnnotation;
   return self.forget();
 }
 
+/* static */ already_AddRefed<nsIUrlClassifierFeature>
+UrlClassifierFeatureTrackingAnnotation::GetIfNameMatches(
+    const nsACString& aName) {
+  MOZ_ASSERT(gFeatureTrackingAnnotation);
+
+  if (!aName.EqualsLiteral(TRACKING_ANNOTATION_FEATURE_NAME)) {
+    return nullptr;
+  }
+
+  RefPtr<UrlClassifierFeatureTrackingAnnotation> self =
+      gFeatureTrackingAnnotation;
+  return self.forget();
+}
+
 NS_IMETHODIMP
 UrlClassifierFeatureTrackingAnnotation::ProcessChannel(nsIChannel* aChannel,
                                                        const nsACString& aList,
                                                        bool* aShouldContinue) {
   NS_ENSURE_ARG_POINTER(aChannel);
   NS_ENSURE_ARG_POINTER(aShouldContinue);
 
   // This is not a blocking feature.
--- a/netwerk/url-classifier/UrlClassifierFeatureTrackingAnnotation.h
+++ b/netwerk/url-classifier/UrlClassifierFeatureTrackingAnnotation.h
@@ -19,16 +19,19 @@ class UrlClassifierFeatureTrackingAnnota
  public:
   static void Initialize();
 
   static void Shutdown();
 
   static already_AddRefed<UrlClassifierFeatureTrackingAnnotation> MaybeCreate(
       nsIChannel* aChannel);
 
+  static already_AddRefed<nsIUrlClassifierFeature> GetIfNameMatches(
+      const nsACString& aName);
+
   NS_IMETHOD ProcessChannel(nsIChannel* aChannel, const nsACString& aList,
                             bool* aShouldContinue) override;
 
   NS_IMETHOD GetURIByListType(nsIChannel* aChannel,
                               nsIUrlClassifierFeature::listType aListType,
                               nsIURI** aURI) override;
 
  private:
--- a/netwerk/url-classifier/UrlClassifierFeatureTrackingProtection.cpp
+++ b/netwerk/url-classifier/UrlClassifierFeatureTrackingProtection.cpp
@@ -12,32 +12,34 @@
 #include "nsILoadContext.h"
 #include "nsNetUtil.h"
 
 namespace mozilla {
 namespace net {
 
 namespace {
 
+#define TRACKING_PROTECTION_FEATURE_NAME "tracking-protection"
+
 #define URLCLASSIFIER_TRACKING_BLACKLIST "urlclassifier.trackingTable"
 #define URLCLASSIFIER_TRACKING_BLACKLIST_TEST_ENTRIES \
   "urlclassifier.trackingTable.testEntries"
 #define URLCLASSIFIER_TRACKING_WHITELIST "urlclassifier.trackingWhitelistTable"
 #define URLCLASSIFIER_TRACKING_WHITELIST_TEST_ENTRIES \
   "urlclassifier.trackingWhitelistTable.testEntries"
 #define TABLE_TRACKING_BLACKLIST_PREF "tracking-blacklist-pref"
 #define TABLE_TRACKING_WHITELIST_PREF "tracking-whitelist-pref"
 
 StaticRefPtr<UrlClassifierFeatureTrackingProtection> gFeatureTrackingProtection;
 
 }  // namespace
 
 UrlClassifierFeatureTrackingProtection::UrlClassifierFeatureTrackingProtection()
     : UrlClassifierFeatureBase(
-          NS_LITERAL_CSTRING("tracking-protection"),
+          NS_LITERAL_CSTRING(TRACKING_PROTECTION_FEATURE_NAME),
           NS_LITERAL_CSTRING(URLCLASSIFIER_TRACKING_BLACKLIST),
           NS_LITERAL_CSTRING(URLCLASSIFIER_TRACKING_WHITELIST),
           NS_LITERAL_CSTRING(URLCLASSIFIER_TRACKING_BLACKLIST_TEST_ENTRIES),
           NS_LITERAL_CSTRING(URLCLASSIFIER_TRACKING_WHITELIST_TEST_ENTRIES),
           NS_LITERAL_CSTRING(TABLE_TRACKING_BLACKLIST_PREF),
           NS_LITERAL_CSTRING(TABLE_TRACKING_WHITELIST_PREF), EmptyCString()) {}
 
 /* static */ void UrlClassifierFeatureTrackingProtection::Initialize() {
@@ -98,16 +100,30 @@ UrlClassifierFeatureTrackingProtection::
     return nullptr;
   }
 
   RefPtr<UrlClassifierFeatureTrackingProtection> self =
       gFeatureTrackingProtection;
   return self.forget();
 }
 
+/* static */ already_AddRefed<nsIUrlClassifierFeature>
+UrlClassifierFeatureTrackingProtection::GetIfNameMatches(
+    const nsACString& aName) {
+  MOZ_ASSERT(gFeatureTrackingProtection);
+
+  if (!aName.EqualsLiteral(TRACKING_PROTECTION_FEATURE_NAME)) {
+    return nullptr;
+  }
+
+  RefPtr<UrlClassifierFeatureTrackingProtection> self =
+      gFeatureTrackingProtection;
+  return self.forget();
+}
+
 NS_IMETHODIMP
 UrlClassifierFeatureTrackingProtection::ProcessChannel(nsIChannel* aChannel,
                                                        const nsACString& aList,
                                                        bool* aShouldContinue) {
   NS_ENSURE_ARG_POINTER(aChannel);
   NS_ENSURE_ARG_POINTER(aShouldContinue);
 
   // This is a blocking feature.
--- a/netwerk/url-classifier/UrlClassifierFeatureTrackingProtection.h
+++ b/netwerk/url-classifier/UrlClassifierFeatureTrackingProtection.h
@@ -19,16 +19,19 @@ class UrlClassifierFeatureTrackingProtec
  public:
   static void Initialize();
 
   static void Shutdown();
 
   static already_AddRefed<UrlClassifierFeatureTrackingProtection> MaybeCreate(
       nsIChannel* aChannel);
 
+  static already_AddRefed<nsIUrlClassifierFeature> GetIfNameMatches(
+      const nsACString& aName);
+
   NS_IMETHOD ProcessChannel(nsIChannel* aChannel, const nsACString& aList,
                             bool* aShouldContinue) override;
 
   NS_IMETHOD GetURIByListType(nsIChannel* aChannel,
                               nsIUrlClassifierFeature::listType aListType,
                               nsIURI** aURI) override;
 
  private:
--- a/netwerk/url-classifier/moz.build
+++ b/netwerk/url-classifier/moz.build
@@ -17,16 +17,17 @@ XPIDL_MODULE = 'url-classifier'
 DEFINES['GOOGLE_PROTOBUF_NO_RTTI'] = True
 DEFINES['GOOGLE_PROTOBUF_NO_STATIC_INITIALIZER'] = True
 
 UNIFIED_SOURCES += [
     'AsyncUrlChannelClassifier.cpp',
     'nsChannelClassifier.cpp',
     'UrlClassifierCommon.cpp',
     'UrlClassifierFeatureBase.cpp',
+    'UrlClassifierFeatureCustomTables.cpp',
     'UrlClassifierFeatureFactory.cpp',
     'UrlClassifierFeatureFlash.cpp',
     'UrlClassifierFeatureLoginReputation.cpp',
     'UrlClassifierFeatureResult.cpp',
     'UrlClassifierFeatureTrackingAnnotation.cpp',
     'UrlClassifierFeatureTrackingProtection.cpp',
 ]
 
--- a/netwerk/url-classifier/nsIURIClassifier.idl
+++ b/netwerk/url-classifier/nsIURIClassifier.idl
@@ -100,16 +100,29 @@ interface nsIURIClassifier : nsISupports
    * network requests.
    */
   void asyncClassifyLocalWithFeatures(in nsIURI aURI,
                                       in Array<nsIUrlClassifierFeature> aFeatures,
                                       in nsIUrlClassifierFeature_listType aListType,
                                       in nsIUrlClassifierFeatureCallback aCallback);
 
   /**
+   * Returns a feature named aFeatureName.
+   */
+  nsIUrlClassifierFeature getFeatureByName(in ACString aFeatureName);
+
+  /**
+   * Create a new feature with a list of tables. This method is just for
+   * testing! Don't use it elsewhere.
+   */
+  nsIUrlClassifierFeature createFeatureWithTables(in ACString aName,
+                                                  in Array<ACString> aBlacklistTables,
+                                                  in Array<ACString> aWhitelistTables);
+
+  /**
    * Report to the provider that a Safe Browsing warning was shown.
    *
    * @param aChannel
    *        Channel for which the URL matched something on the threat list.
    * @param aProvider
    *        Provider to notify.
    * @param aList
    *        List where the full hash was found.
--- a/testing/specialpowers/content/specialpowersAPI.js
+++ b/testing/specialpowers/content/specialpowersAPI.js
@@ -2240,29 +2240,31 @@ SpecialPowersAPI.prototype = {
                                       tpEnabled, wrapCallback);
   },
 
   // TODO: Bug 1353701 - Supports custom event target for labelling.
   doUrlClassifyLocal(uri, tables, callback) {
     let classifierService =
       Cc["@mozilla.org/url-classifier/dbservice;1"].getService(Ci.nsIURIClassifier);
 
-    let wrapCallback = (...args) => {
+    let wrapCallback = results => {
       Services.tm.dispatchToMainThread(() => {
         if (typeof callback == "function") {
-          callback(...args);
+          callback(wrapIfUnwrapped(results));
         } else {
-          callback.onClassifyComplete.call(undefined, ...args);
+          callback.onClassifyComplete.call(undefined, wrapIfUnwrapped(results));
         }
       });
     };
 
-    return classifierService.asyncClassifyLocalWithTables(unwrapIfWrapped(uri),
-                                                          tables, [], [],
-                                                          wrapCallback);
+    let feature = classifierService.createFeatureWithTables("test", tables.split(","), []);
+    return classifierService.asyncClassifyLocalWithFeatures(unwrapIfWrapped(uri),
+                                                            [feature],
+                                                            Ci.nsIUrlClassifierFeature.blacklist,
+                                                            wrapCallback);
   },
 
   EARLY_BETA_OR_EARLIER: AppConstants.EARLY_BETA_OR_EARLIER,
 
 };
 
 this.SpecialPowersAPI = SpecialPowersAPI;
 this.bindDOMWindowUtils = bindDOMWindowUtils;
--- a/toolkit/components/url-classifier/UrlClassifierTelemetryUtils.h
+++ b/toolkit/components/url-classifier/UrlClassifierTelemetryUtils.h
@@ -2,16 +2,17 @@
 /* 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/. */
 
 #ifndef UrlClassifierTelemetryUtils_h__
 #define UrlClassifierTelemetryUtils_h__
 
 #include "mozilla/TypedEnumBits.h"
+#include "nsISupportsImpl.h"
 
 namespace mozilla {
 namespace safebrowsing {
 
 // We might need to expand the bucket here if telemetry shows lots of errors
 // are neither connection errors nor DNS errors.
 uint8_t NetworkErrorToBucket(nsresult rv);
 
--- a/toolkit/components/url-classifier/nsUrlClassifierDBService.cpp
+++ b/toolkit/components/url-classifier/nsUrlClassifierDBService.cpp
@@ -42,16 +42,17 @@
 #include "mozilla/Attributes.h"
 #include "nsIPrincipal.h"
 #include "Classifier.h"
 #include "ProtocolParser.h"
 #include "nsContentUtils.h"
 #include "mozilla/dom/ContentChild.h"
 #include "mozilla/dom/PermissionMessageUtils.h"
 #include "mozilla/dom/URLClassifierChild.h"
+#include "mozilla/net/UrlClassifierFeatureFactory.h"
 #include "mozilla/net/UrlClassifierFeatureResult.h"
 #include "mozilla/ipc/URIUtils.h"
 #include "mozilla/SyncRunnable.h"
 #include "nsProxyRelease.h"
 #include "UrlClassifierTelemetryUtils.h"
 #include "nsIURLFormatter.h"
 #include "nsIUploadChannel.h"
 #include "nsStringStream.h"
@@ -2847,8 +2848,39 @@ bool nsUrlClassifierDBService::AsyncClas
   nsCOMPtr<nsIUrlClassifierFeatureCallback> callback(aCallback);
   nsCOMPtr<nsIRunnable> cbRunnable = NS_NewRunnableFunction(
       "nsUrlClassifierDBService::AsyncClassifyLocalWithFeatures",
       [callback, results]() { callback->OnClassifyComplete(results); });
 
   NS_DispatchToMainThread(cbRunnable);
   return true;
 }
+
+NS_IMETHODIMP
+nsUrlClassifierDBService::GetFeatureByName(const nsACString& aFeatureName,
+                                           nsIUrlClassifierFeature** aFeature) {
+  NS_ENSURE_ARG_POINTER(aFeature);
+  nsCOMPtr<nsIUrlClassifierFeature> feature =
+      mozilla::net::UrlClassifierFeatureFactory::GetFeatureByName(aFeatureName);
+  if (NS_WARN_IF(!feature)) {
+    return NS_ERROR_FAILURE;
+  }
+
+  feature.forget(aFeature);
+  return NS_OK;
+}
+
+NS_IMETHODIMP
+nsUrlClassifierDBService::CreateFeatureWithTables(
+    const nsACString& aName, const nsTArray<nsCString>& aBlacklistTables,
+    const nsTArray<nsCString>& aWhitelistTables,
+    nsIUrlClassifierFeature** aFeature) {
+  NS_ENSURE_ARG_POINTER(aFeature);
+  nsCOMPtr<nsIUrlClassifierFeature> feature =
+      mozilla::net::UrlClassifierFeatureFactory::CreateFeatureWithTables(
+          aName, aBlacklistTables, aWhitelistTables);
+  if (NS_WARN_IF(!feature)) {
+    return NS_ERROR_FAILURE;
+  }
+
+  feature.forget(aFeature);
+  return NS_OK;
+}
--- a/toolkit/components/url-classifier/tests/mochitest/test_classifier.html
+++ b/toolkit/components/url-classifier/tests/mochitest/test_classifier.html
@@ -142,19 +142,25 @@ function testService() {
       }
       let test = testURLs.shift();
       let tables = "test-malware-simple,test-unwanted-simple,test-phish-simple,test-track-simple,test-block-simple,test-harmful-simple";
       let uri = SpecialPowers.Services.io.newURI(test.url);
       let prin = SpecialPowers.Services.scriptSecurityManager.createCodebasePrincipal(uri, {});
       SpecialPowers.doUrlClassify(prin, null, test.trackingProtection, function(errorCode) {
         is(errorCode, test.result,
            `Successful asynchronous classification of ${test.url} with TP=${test.trackingProtection}`);
-        SpecialPowers.doUrlClassifyLocal(uri, tables, function(errorCode1, tables1) {
-          is(tables1, test.table,
-             `Successful asynchronous local classification of ${test.url} with TP=${test.trackingProtection}`);
+        SpecialPowers.doUrlClassifyLocal(uri, tables, function(results) {
+          if (results.length == 0) {
+            is(test.table, "",
+               `Successful asynchronous local classification of ${test.url} with TP=${test.trackingProtection}`);
+          } else {
+            let result = results[0].QueryInterface(Ci.nsIUrlClassifierFeatureResult);
+            is(result.list, test.table,
+               `Successful asynchronous local classification of ${test.url} with TP=${test.trackingProtection}`);
+          }
           runNextTest();
         });
 
       });
     }
     runNextTest(resolve);
   });
 }
new file mode 100644
--- /dev/null
+++ b/toolkit/components/url-classifier/tests/unit/test_features.js
@@ -0,0 +1,61 @@
+/* Any copyright is dedicated to the Public Domain.
+   http://creativecommons.org/publicdomain/zero/1.0/
+*/
+
+"use strict";
+
+add_test(async _ => {
+  Services.prefs.setBoolPref("browser.safebrowsing.passwords.enabled", true);
+
+  let classifier = Cc["@mozilla.org/url-classifier/dbservice;1"]
+                     .getService(Ci.nsIURIClassifier);
+  ok(!!classifier, "We have the URI-Classifier");
+
+  var tests = [
+    { name: "a", expectedResult: false },
+    { name: "tracking-annotation", expectedResult: true },
+    { name: "tracking-protection", expectedResult: true },
+    { name: "login-reputation", expectedResult: true },
+  ];
+
+  tests.forEach(test => {
+    let feature;
+    try {
+      feature = classifier.getFeatureByName(test.name);
+    } catch (e) {
+    }
+
+    equal(!!feature, test.expectedResult, "Exceptected result for: " + test.name);
+    if (feature) {
+      equal(feature.name, test.name, "Feature name matches");
+    }
+  });
+
+  let uri = Services.io.newURI("https://example.com");
+
+  let feature = classifier.getFeatureByName("tracking-protection");
+
+  let results = await new Promise(resolve => {
+    classifier.asyncClassifyLocalWithFeatures(uri, [feature],
+      Ci.nsIUrlClassifierFeature.blacklist,
+      r => { resolve(r); });
+  });
+  equal(results.length, 0, "No tracker");
+
+  Services.prefs.setCharPref("urlclassifier.trackingTable.testEntries", "example.com");
+
+  feature = classifier.getFeatureByName("tracking-protection");
+
+  results = await new Promise(resolve => {
+    classifier.asyncClassifyLocalWithFeatures(uri, [feature],
+      Ci.nsIUrlClassifierFeature.blacklist,
+      r => { resolve(r); });
+  });
+  equal(results.length, 1, "Tracker");
+  let result = results[0];
+  equal(result.feature.name, "tracking-protection", "Correct feature");
+  equal(result.list, "tracking-blacklist-pref", "Correct list");
+
+  Services.prefs.clearUserPref("browser.safebrowsing.password.enabled");
+  run_next_test();
+});
--- a/toolkit/components/url-classifier/tests/unit/xpcshell.ini
+++ b/toolkit/components/url-classifier/tests/unit/xpcshell.ini
@@ -18,9 +18,10 @@ support-files =
 [test_threat_type_conversion.js]
 [test_provider_url.js]
 [test_streamupdater.js]
 [test_digest256.js]
 [test_listmanager.js]
 [test_pref.js]
 [test_malwaretable_pref.js]
 [test_safebrowsing_protobuf.js]
-[test_platform_specific_threats.js]
\ No newline at end of file
+[test_platform_specific_threats.js]
+[test_features.js]