Merge mozilla-central to autoland. a=merge CLOSED TREE
authorGurzau Raul <rgurzau@mozilla.com>
Sat, 05 Jan 2019 14:40:39 +0200
changeset 452639 61bc6ebb639b73924654f8399d38ef75ace23a1b
parent 452638 94cd5fa46f95d1109e31750b4708c324a5de749e (current diff)
parent 452635 ab2bdfc3132a2f88658b554ca12fae9fd2977928 (diff)
child 452640 599de612de7f822ef74d5ebc10c58942666585ef
push id35319
push userdluca@mozilla.com
push dateSat, 05 Jan 2019 21:51:45 +0000
treeherdermozilla-central@e54046e15abe [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersmerge
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
Merge mozilla-central to autoland. a=merge CLOSED TREE
--- 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]