Merge inbound to mozilla-central. a=merge
authorGurzau Raul <rgurzau@mozilla.com>
Sat, 05 Jan 2019 14:37:09 +0200
changeset 452635 ab2bdfc3132a2f88658b554ca12fae9fd2977928
parent 452630 689de183c7619741db70d37451e2cd2655909ba4 (current diff)
parent 452634 dfd83fc53601bd3966fa28628b501f59c43d0821 (diff)
child 452639 61bc6ebb639b73924654f8399d38ef75ace23a1b
child 452642 e4f3ad7a95af8aa3ae7950cee19b79992d89f2c4
push id35318
push userrgurzau@mozilla.com
push dateSat, 05 Jan 2019 12:37:39 +0000
treeherdermozilla-central@ab2bdfc3132a [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersmerge
milestone66.0a1
first release with
nightly linux32
ab2bdfc3132a / 66.0a1 / 20190105123739 / files
nightly linux64
ab2bdfc3132a / 66.0a1 / 20190105123739 / files
nightly mac
ab2bdfc3132a / 66.0a1 / 20190105123739 / files
nightly win32
ab2bdfc3132a / 66.0a1 / 20190105123739 / files
nightly win64
ab2bdfc3132a / 66.0a1 / 20190105123739 / files
last release without
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
releases
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
Merge inbound to mozilla-central. a=merge
--- 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) {
--- a/layout/build/nsLayoutStatics.cpp
+++ b/layout/build/nsLayoutStatics.cpp
@@ -290,18 +290,16 @@ nsresult nsLayoutStatics::Initialize() {
 
   mozilla::Fuzzyfox::Start();
 
   ClearSiteData::Initialize();
 
   // Reporting API.
   ReportingHeader::Initialize();
 
-  mozilla::net::UrlClassifierFeatureFactory::Initialize();
-
   return NS_OK;
 }
 
 void nsLayoutStatics::Shutdown() {
   // Don't need to shutdown nsWindowMemoryReporter, that will be done by the
   // memory reporter manager.
 
   if (XRE_IsParentProcess() || XRE_IsContentProcess()) {
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,43 +6,33 @@
 
 #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.
-  if (!XRE_IsParentProcess()) {
-    return;
-  }
-
-  UrlClassifierFeatureFlash::Initialize();
-  UrlClassifierFeatureTrackingAnnotation::Initialize();
-  UrlClassifierFeatureTrackingProtection::Initialize();
-}
-
 /* static */ void UrlClassifierFeatureFactory::Shutdown() {
   // We want to expose Features only in the parent process.
   if (!XRE_IsParentProcess()) {
     return;
   }
 
-  UrlClassifierFeatureFlash::Shutdown();
+  UrlClassifierFeatureFlash::MaybeShutdown();
   UrlClassifierFeatureLoginReputation::MaybeShutdown();
-  UrlClassifierFeatureTrackingAnnotation::Shutdown();
-  UrlClassifierFeatureTrackingProtection::Shutdown();
+  UrlClassifierFeatureTrackingAnnotation::MaybeShutdown();
+  UrlClassifierFeatureTrackingProtection::MaybeShutdown();
 }
 
 /* static */ void UrlClassifierFeatureFactory::GetFeaturesFromChannel(
     nsIChannel* aChannel,
     nsTArray<nsCOMPtr<nsIUrlClassifierFeature>>& aFeatures) {
   MOZ_ASSERT(XRE_IsParentProcess());
   MOZ_ASSERT(aChannel);
 
@@ -72,10 +62,55 @@ namespace net {
 }
 
 /* static */
 nsIUrlClassifierFeature*
 UrlClassifierFeatureFactory::GetFeatureLoginReputation() {
   return UrlClassifierFeatureLoginReputation::MaybeGetOrCreate();
 }
 
+/* static */ already_AddRefed<nsIUrlClassifierFeature>
+UrlClassifierFeatureFactory::GetFeatureByName(const nsACString& aName) {
+  if (!XRE_IsParentProcess()) {
+    return nullptr;
+  }
+
+  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
@@ -13,23 +13,28 @@
 class nsIChannel;
 class nsIUrlClassifierFeature;
 
 namespace mozilla {
 namespace net {
 
 class UrlClassifierFeatureFactory final {
  public:
-  static void Initialize();
-
   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
@@ -29,16 +29,18 @@ static FlashFeatures sFlashFeaturesMap[]
     {"flash-allow", "urlclassifier.flashAllowTable",
      "urlclassifier.flashAllowExceptTable", false,
      nsIHttpChannel::FlashPluginAllowed},
     {"flash-deny-subdoc", "urlclassifier.flashSubDocTable",
      "urlclassifier.flashSubDocExceptTable", true,
      nsIHttpChannel::FlashPluginDeniedInSubdocuments},
 };
 
+bool IsInitialized() { return !!sFlashFeaturesMap[0].mFeature; }
+
 }  // namespace
 
 UrlClassifierFeatureFlash::UrlClassifierFeatureFlash(uint32_t aId)
     : UrlClassifierFeatureBase(
           nsDependentCString(sFlashFeaturesMap[aId].mName),
           nsDependentCString(sFlashFeaturesMap[aId].mBlacklistPrefTables),
           nsDependentCString(sFlashFeaturesMap[aId].mWhitelistPrefTables),
           EmptyCString(),  // aPrefBlacklistHosts
@@ -48,27 +50,37 @@ UrlClassifierFeatureFlash::UrlClassifier
           EmptyCString())  // aPrefSkipHosts
       ,
       mFlashPluginState(sFlashFeaturesMap[aId].mFlashPluginState) {
   static_assert(nsIHttpChannel::FlashPluginDeniedInSubdocuments ==
                     nsIHttpChannel::FlashPluginLastValue,
                 "nsIHttpChannel::FlashPluginLastValue is out-of-sync!");
 }
 
-/* static */ void UrlClassifierFeatureFlash::Initialize() {
+/* static */ void UrlClassifierFeatureFlash::MaybeInitialize() {
+  MOZ_ASSERT(XRE_IsParentProcess());
+
+  if (IsInitialized()) {
+    return;
+  }
+
   uint32_t numFeatures =
       (sizeof(sFlashFeaturesMap) / sizeof(sFlashFeaturesMap[0]));
   for (uint32_t i = 0; i < numFeatures; ++i) {
     MOZ_ASSERT(!sFlashFeaturesMap[i].mFeature);
     sFlashFeaturesMap[i].mFeature = new UrlClassifierFeatureFlash(i);
     sFlashFeaturesMap[i].mFeature->InitializePreferences();
   }
 }
 
-/* static */ void UrlClassifierFeatureFlash::Shutdown() {
+/* static */ void UrlClassifierFeatureFlash::MaybeShutdown() {
+  if (!IsInitialized()) {
+    return;
+  }
+
   uint32_t numFeatures =
       (sizeof(sFlashFeaturesMap) / sizeof(sFlashFeaturesMap[0]));
   for (uint32_t i = 0; i < numFeatures; ++i) {
     MOZ_ASSERT(sFlashFeaturesMap[i].mFeature);
     sFlashFeaturesMap[i].mFeature->ShutdownPreferences();
     sFlashFeaturesMap[i].mFeature = nullptr;
   }
 }
@@ -95,27 +107,47 @@ UrlClassifierFeatureFlash::UrlClassifier
   // Only allow plugins for documents from an HTTP/HTTPS origin.
   if (StaticPrefs::plugins_http_https_only()) {
     nsCOMPtr<nsIHttpChannel> httpChannel = do_QueryInterface(aChannel);
     if (!httpChannel) {
       return;
     }
   }
 
+  MaybeInitialize();
+
   uint32_t numFeatures =
       (sizeof(sFlashFeaturesMap) / sizeof(sFlashFeaturesMap[0]));
   for (uint32_t i = 0; i < numFeatures; ++i) {
     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) {
+  MaybeInitialize();
+
+  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
@@ -9,33 +9,37 @@
 
 #include "UrlClassifierFeatureBase.h"
 
 namespace mozilla {
 namespace net {
 
 class UrlClassifierFeatureFlash final : public UrlClassifierFeatureBase {
  public:
-  static void Initialize();
-  static void Shutdown();
+  static void MaybeShutdown();
 
   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;
 
  private:
   explicit UrlClassifierFeatureFlash(uint32_t aId);
 
+  static void MaybeInitialize();
+
   nsIHttpChannel::FlashPluginState mFlashPluginState;
 };
 
 }  // namespace net
 }  // namespace mozilla
 
 #endif  // mozilla_UrlClassifierFeatureFlash_h
--- 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,58 +106,78 @@ 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)) {}
 
-/* static */ void UrlClassifierFeatureTrackingAnnotation::Initialize() {
-  UC_LOG(("UrlClassifierFeatureTrackingAnnotation: Initializing"));
-  MOZ_ASSERT(!gFeatureTrackingAnnotation);
+/* static */ void UrlClassifierFeatureTrackingAnnotation::MaybeInitialize() {
+  MOZ_ASSERT(XRE_IsParentProcess());
+  UC_LOG(("UrlClassifierFeatureTrackingAnnotation: MaybeInitialize"));
 
-  gFeatureTrackingAnnotation = new UrlClassifierFeatureTrackingAnnotation();
-  gFeatureTrackingAnnotation->InitializePreferences();
+  if (!gFeatureTrackingAnnotation) {
+    gFeatureTrackingAnnotation = new UrlClassifierFeatureTrackingAnnotation();
+    gFeatureTrackingAnnotation->InitializePreferences();
+  }
 }
 
-/* static */ void UrlClassifierFeatureTrackingAnnotation::Shutdown() {
-  UC_LOG(("UrlClassifierFeatureTrackingAnnotation: Shutdown"));
-  MOZ_ASSERT(gFeatureTrackingAnnotation);
+/* static */ void UrlClassifierFeatureTrackingAnnotation::MaybeShutdown() {
+  UC_LOG(("UrlClassifierFeatureTrackingAnnotation: MaybeShutdown"));
 
-  gFeatureTrackingAnnotation->ShutdownPreferences();
-  gFeatureTrackingAnnotation = nullptr;
+  if (gFeatureTrackingAnnotation) {
+    gFeatureTrackingAnnotation->ShutdownPreferences();
+    gFeatureTrackingAnnotation = nullptr;
+  }
 }
 
 /* static */ already_AddRefed<UrlClassifierFeatureTrackingAnnotation>
 UrlClassifierFeatureTrackingAnnotation::MaybeCreate(nsIChannel* aChannel) {
-  MOZ_ASSERT(gFeatureTrackingAnnotation);
   MOZ_ASSERT(aChannel);
 
   UC_LOG(("UrlClassifierFeatureTrackingAnnotation: MaybeCreate for channel %p",
           aChannel));
 
   if (!StaticPrefs::privacy_trackingprotection_annotate_channels()) {
     return nullptr;
   }
 
   if (!UrlClassifierCommon::ShouldEnableTrackingProtectionOrAnnotation(
           aChannel, AntiTrackingCommon::eTrackingAnnotations)) {
     return nullptr;
   }
 
+  MaybeInitialize();
+  MOZ_ASSERT(gFeatureTrackingAnnotation);
+
+  RefPtr<UrlClassifierFeatureTrackingAnnotation> self =
+      gFeatureTrackingAnnotation;
+  return self.forget();
+}
+
+/* static */ already_AddRefed<nsIUrlClassifierFeature>
+UrlClassifierFeatureTrackingAnnotation::GetIfNameMatches(
+    const nsACString& aName) {
+  if (!aName.EqualsLiteral(TRACKING_ANNOTATION_FEATURE_NAME)) {
+    return nullptr;
+  }
+
+  MaybeInitialize();
+  MOZ_ASSERT(gFeatureTrackingAnnotation);
+
   RefPtr<UrlClassifierFeatureTrackingAnnotation> self =
       gFeatureTrackingAnnotation;
   return self.forget();
 }
 
 NS_IMETHODIMP
 UrlClassifierFeatureTrackingAnnotation::ProcessChannel(nsIChannel* aChannel,
                                                        const nsACString& aList,
--- a/netwerk/url-classifier/UrlClassifierFeatureTrackingAnnotation.h
+++ b/netwerk/url-classifier/UrlClassifierFeatureTrackingAnnotation.h
@@ -12,30 +12,33 @@
 class nsIChannel;
 
 namespace mozilla {
 namespace net {
 
 class UrlClassifierFeatureTrackingAnnotation final
     : public UrlClassifierFeatureBase {
  public:
-  static void Initialize();
-
-  static void Shutdown();
+  static void MaybeShutdown();
 
   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:
   UrlClassifierFeatureTrackingAnnotation();
+
+  static void MaybeInitialize();
 };
 
 }  // namespace net
 }  // namespace mozilla
 
 #endif  // mozilla_net_UrlClassifierFeatureTrackingAnnotation_h
--- a/netwerk/url-classifier/UrlClassifierFeatureTrackingProtection.cpp
+++ b/netwerk/url-classifier/UrlClassifierFeatureTrackingProtection.cpp
@@ -12,58 +12,62 @@
 #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() {
-  UC_LOG(("UrlClassifierFeatureTrackingProtection: Initializing"));
-  MOZ_ASSERT(!gFeatureTrackingProtection);
+/* static */ void UrlClassifierFeatureTrackingProtection::MaybeInitialize() {
+  MOZ_ASSERT(XRE_IsParentProcess());
+  UC_LOG(("UrlClassifierFeatureTrackingProtection: MaybeInitialize"));
 
-  gFeatureTrackingProtection = new UrlClassifierFeatureTrackingProtection();
-  gFeatureTrackingProtection->InitializePreferences();
+  if (!gFeatureTrackingProtection) {
+    gFeatureTrackingProtection = new UrlClassifierFeatureTrackingProtection();
+    gFeatureTrackingProtection->InitializePreferences();
+  }
 }
 
-/* static */ void UrlClassifierFeatureTrackingProtection::Shutdown() {
+/* static */ void UrlClassifierFeatureTrackingProtection::MaybeShutdown() {
   UC_LOG(("UrlClassifierFeatureTrackingProtection: Shutdown"));
-  MOZ_ASSERT(gFeatureTrackingProtection);
 
-  gFeatureTrackingProtection->ShutdownPreferences();
-  gFeatureTrackingProtection = nullptr;
+  if (gFeatureTrackingProtection) {
+    gFeatureTrackingProtection->ShutdownPreferences();
+    gFeatureTrackingProtection = nullptr;
+  }
 }
 
 /* static */ already_AddRefed<UrlClassifierFeatureTrackingProtection>
 UrlClassifierFeatureTrackingProtection::MaybeCreate(nsIChannel* aChannel) {
-  MOZ_ASSERT(gFeatureTrackingProtection);
   MOZ_ASSERT(aChannel);
 
   UC_LOG(("UrlClassifierFeatureTrackingProtection: MaybeCreate for channel %p",
           aChannel));
 
   nsCOMPtr<nsILoadContext> loadContext;
   NS_QueryNotificationCallbacks(aChannel, loadContext);
   if (!loadContext || !loadContext->UseTrackingProtection()) {
@@ -93,16 +97,34 @@ UrlClassifierFeatureTrackingProtection::
     return nullptr;
   }
 
   if (!UrlClassifierCommon::ShouldEnableTrackingProtectionOrAnnotation(
           aChannel, AntiTrackingCommon::eTrackingProtection)) {
     return nullptr;
   }
 
+  MaybeInitialize();
+  MOZ_ASSERT(gFeatureTrackingProtection);
+
+  RefPtr<UrlClassifierFeatureTrackingProtection> self =
+      gFeatureTrackingProtection;
+  return self.forget();
+}
+
+/* static */ already_AddRefed<nsIUrlClassifierFeature>
+UrlClassifierFeatureTrackingProtection::GetIfNameMatches(
+    const nsACString& aName) {
+  if (!aName.EqualsLiteral(TRACKING_PROTECTION_FEATURE_NAME)) {
+    return nullptr;
+  }
+
+  MaybeInitialize();
+  MOZ_ASSERT(gFeatureTrackingProtection);
+
   RefPtr<UrlClassifierFeatureTrackingProtection> self =
       gFeatureTrackingProtection;
   return self.forget();
 }
 
 NS_IMETHODIMP
 UrlClassifierFeatureTrackingProtection::ProcessChannel(nsIChannel* aChannel,
                                                        const nsACString& aList,
--- a/netwerk/url-classifier/UrlClassifierFeatureTrackingProtection.h
+++ b/netwerk/url-classifier/UrlClassifierFeatureTrackingProtection.h
@@ -12,30 +12,33 @@
 class nsIChannel;
 
 namespace mozilla {
 namespace net {
 
 class UrlClassifierFeatureTrackingProtection final
     : public UrlClassifierFeatureBase {
  public:
-  static void Initialize();
-
-  static void Shutdown();
+  static void MaybeShutdown();
 
   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:
   UrlClassifierFeatureTrackingProtection();
+
+  static void MaybeInitialize();
 };
 
 }  // namespace net
 }  // namespace mozilla
 
 #endif  // mozilla_net_UrlClassifierFeatureTrackingProtection_h
--- 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
@@ -77,39 +77,38 @@ interface nsIURIClassifier : nsISupports
    *         callback will be called.
    */
   boolean classify(in nsIPrincipal aPrincipal,
                    in nsIEventTarget aEventTarget,
                    in boolean aTrackingProtectionEnabled,
                    in nsIURIClassifierCallback aCallback);
 
   /**
-   * Asynchronously classify a URI with a comma-separated string
-   * containing the given tables. This does not make network requests.
-   * The callback does NOT totally follow nsIURIClassifierCallback's
-   * semantics described above. Only |aList| will be meaningful, which
-   * is a comma separated list of table names. (same as what classifyLocal
-   * returns.)
-   */
-  void asyncClassifyLocalWithTables(in nsIURI aURI,
-                                    in ACString aTables,
-                                    in Array<ACString> aExtraTablesByPrefs,
-                                    in Array<ACString> aExtraEntriesByPrefs,
-                                    in nsIURIClassifierCallback aCallback);
-
-  /**
    * Asynchronously classify a URI with list of features. This does not make
    * 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"
@@ -235,141 +236,16 @@ class FeatureHolder final {
     return tableData;
   }
 
   nsCOMPtr<nsIURI> mURI;
   nsTArray<FeatureData> mFeatureData;
   nsTArray<RefPtr<TableData>> mTableData;
 };
 
-// Simple feature which wraps preferences and tables received by
-// AsyncClassifyLocalWithTables() as arguments.
-class DummyFeature final : public nsIUrlClassifierFeature {
- public:
-  NS_DECL_ISUPPORTS
-
-  enum Type { ePreference, eTable };
-
-  explicit DummyFeature(const nsACString& aName)
-      : mType(eTable), mName(aName) {}
-
-  explicit DummyFeature(const nsACString& aPreference,
-                        nsTArray<nsCString>& aHostsFromPreference)
-      : mType(ePreference),
-        mName(aPreference),
-        mHostsFromPreference(aHostsFromPreference) {}
-
-  NS_IMETHOD
-  GetName(nsACString& aName) override {
-    aName = mName;
-    return NS_OK;
-  }
-
-  NS_IMETHOD
-  GetTables(nsIUrlClassifierFeature::listType,
-            nsTArray<nsCString>& aTables) override {
-    if (mType == eTable) {
-      aTables.AppendElement(mName);
-    }
-    return NS_OK;
-  }
-
-  NS_IMETHOD
-  HasTable(const nsACString& aTable, nsIUrlClassifierFeature::listType,
-           bool* aResult) override {
-    NS_ENSURE_ARG_POINTER(aResult);
-    *aResult = mType == eTable && aTable == mName;
-    return NS_OK;
-  }
-
-  NS_IMETHOD
-  HasHostInPreferences(const nsACString& aHost,
-                       nsIUrlClassifierFeature::listType,
-                       nsACString& aPrefTableName, bool* aResult) override {
-    NS_ENSURE_ARG_POINTER(aResult);
-    *aResult = mHostsFromPreference.Contains(aHost);
-    aPrefTableName = mName;
-    return NS_OK;
-  }
-
-  NS_IMETHOD
-  GetSkipHostList(nsACString& aList) override {
-    // Nothing to do here.
-    return NS_OK;
-  }
-
-  NS_IMETHOD
-  ProcessChannel(nsIChannel* aChannel, const nsACString& aList,
-                 bool* aShouldContinue) override {
-    NS_ENSURE_ARG_POINTER(aShouldContinue);
-    *aShouldContinue = true;
-
-    // Nothing to do here.
-    return NS_OK;
-  }
-
-  NS_IMETHODIMP
-  GetURIByListType(nsIChannel* aChannel,
-                   nsIUrlClassifierFeature::listType aListType,
-                   nsIURI** aURI) override {
-    return NS_ERROR_NOT_IMPLEMENTED;
-  }
-
- private:
-  ~DummyFeature() = default;
-
-  Type mType;
-  nsCString mName;
-  nsTArray<nsCString> mHostsFromPreference;
-};
-
-NS_IMPL_ISUPPORTS(DummyFeature, nsIUrlClassifierFeature)
-
-// This class is a proxy from nsIUrlClassifierFeatureCallback to
-// nsIUrlClassifierCallback.
-class CallbackWrapper final : public nsIUrlClassifierFeatureCallback {
- public:
-  NS_DECL_ISUPPORTS
-
-  explicit CallbackWrapper(nsIURIClassifierCallback* aCallback)
-      : mCallback(aCallback) {
-    MOZ_ASSERT(aCallback);
-  }
-
-  NS_IMETHOD
-  OnClassifyComplete(const nsTArray<RefPtr<nsIUrlClassifierFeatureResult>>&
-                         aResults) override {
-    nsAutoCString finalList;
-
-    for (nsIUrlClassifierFeatureResult* result : aResults) {
-      const nsCString& list =
-          static_cast<mozilla::net::UrlClassifierFeatureResult*>(result)
-              ->List();
-      MOZ_ASSERT(!list.IsEmpty());
-
-      if (!finalList.IsEmpty()) {
-        finalList.AppendLiteral(",");
-      }
-
-      finalList.Append(list);
-    }
-
-    mCallback->OnClassifyComplete(NS_OK, finalList, EmptyCString(),
-                                  EmptyCString());
-    return NS_OK;
-  }
-
- private:
-  ~CallbackWrapper() = default;
-
-  nsCOMPtr<nsIURIClassifierCallback> mCallback;
-};
-
-NS_IMPL_ISUPPORTS(CallbackWrapper, nsIUrlClassifierFeatureCallback)
-
 }  // namespace
 
 using namespace mozilla;
 using namespace mozilla::safebrowsing;
 
 // MOZ_LOG=UrlClassifierDbService:5
 LazyLogModule gUrlClassifierDbServiceLog("UrlClassifierDbService");
 #define LOG(args) \
@@ -1998,60 +1874,16 @@ nsUrlClassifierDBService::Classify(nsIPr
     // The URI had no hostname, don't try to classify it.
     return NS_OK;
   }
   NS_ENSURE_SUCCESS(rv, rv);
 
   return NS_OK;
 }
 
-NS_IMETHODIMP
-nsUrlClassifierDBService::AsyncClassifyLocalWithTables(
-    nsIURI* aURI, const nsACString& aTables,
-    const nsTArray<nsCString>& aExtraTablesByPrefs,
-    const nsTArray<nsCString>& aExtraEntriesByPrefs,
-    nsIURIClassifierCallback* aCallback) {
-  MOZ_ASSERT(NS_IsMainThread(),
-             "AsyncClassifyLocalWithTables must be called "
-             "on main thread");
-
-  if (aExtraTablesByPrefs.Length() != aExtraEntriesByPrefs.Length()) {
-    return NS_ERROR_FAILURE;
-  }
-
-  if (gShuttingDownThread) {
-    return NS_ERROR_ABORT;
-  }
-
-  // Let's convert the current params in a list of features.
-  nsTArray<RefPtr<nsIUrlClassifierFeature>> features;
-
-  for (uint32_t i = 0; i < aExtraTablesByPrefs.Length(); ++i) {
-    nsTArray<nsCString> hosts;
-    Classifier::SplitTables(aExtraEntriesByPrefs[i], hosts);
-    RefPtr<DummyFeature> feature =
-        new DummyFeature(aExtraTablesByPrefs[i], hosts);
-    features.AppendElement(feature);
-  }
-
-  nsTArray<nsCString> tables;
-  Classifier::SplitTables(aTables, tables);
-  for (uint32_t i = 0; i < tables.Length(); ++i) {
-    RefPtr<DummyFeature> feature = new DummyFeature(tables[i]);
-    features.AppendElement(feature);
-  }
-
-  RefPtr<CallbackWrapper> callback = new CallbackWrapper(aCallback);
-
-  // Doesn't really matter if we pass blacklist, whitelist or any other list
-  // here because the DummyFeature returns always the same values.
-  return AsyncClassifyLocalWithFeatures(
-      aURI, features, nsIUrlClassifierFeature::blacklist, callback);
-}
-
 class ThreatHitReportListener final : public nsIStreamListener {
  public:
   NS_DECL_ISUPPORTS
   NS_DECL_NSIREQUESTOBSERVER
   NS_DECL_NSISTREAMLISTENER
 
   ThreatHitReportListener() = default;
 
@@ -2312,17 +2144,17 @@ nsresult nsUrlClassifierDBService::Looku
       nsTArray<nsCString> entries;
       Classifier::SplitTables(aExtraEntriesByPrefs[i], entries);
       if (entries.Contains(host)) {
         *didLookup = true;
 
         nsCString table = aExtraTablesByPrefs[i];
         nsCOMPtr<nsIUrlClassifierCallback> callback(c);
         nsCOMPtr<nsIRunnable> cbRunnable = NS_NewRunnableFunction(
-            "nsUrlClassifierDBService::AsyncClassifyLocalWithTables",
+            "nsUrlClassifierDBService::LookupURI",
             [callback, table]() -> void { callback->HandleEvent(table); });
 
         NS_DispatchToMainThread(cbRunnable);
         return NS_OK;
       }
     }
   }
 
@@ -2847,8 +2679,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]