Bug 1516133 - Avoid extra main-thread jumps during the URL-classification, r=dimi
☠☠ backed out by 9604eba24039 ☠ ☠
authorAndrea Marchesini <amarchesini@mozilla.com>
Thu, 17 Jan 2019 09:33:25 +0100
changeset 514256 cf370ec3969ab8eedeca3c369f83a5de0729c612
parent 514255 683f9ffb0443ae71eabf1c78d4fa65223e7b0549
child 514257 9604eba240396369bb49a59486306d3f543f7f7f
push id1953
push userffxbld-merge
push dateMon, 11 Mar 2019 12:10:20 +0000
treeherdermozilla-release@9c35dcbaa899 [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersdimi
bugs1516133
milestone66.0a1
first release with
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
last release without
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
Bug 1516133 - Avoid extra main-thread jumps during the URL-classification, r=dimi
netwerk/url-classifier/AsyncUrlChannelClassifier.cpp
toolkit/components/url-classifier/nsUrlClassifierDBService.cpp
toolkit/components/url-classifier/nsUrlClassifierDBService.h
--- a/netwerk/url-classifier/AsyncUrlChannelClassifier.cpp
+++ b/netwerk/url-classifier/AsyncUrlChannelClassifier.cpp
@@ -1,459 +1,754 @@
 /* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
 /* vim:set expandtab ts=4 sw=2 sts=2 cin: */
 /* 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 "Classifier.h"
 #include "mozilla/ErrorNames.h"
 #include "mozilla/net/AsyncUrlChannelClassifier.h"
 #include "mozilla/net/UrlClassifierCommon.h"
 #include "mozilla/net/UrlClassifierFeatureFactory.h"
 #include "mozilla/net/UrlClassifierFeatureResult.h"
 #include "nsContentUtils.h"
 #include "nsIChannel.h"
 #include "nsIHttpChannel.h"
 #include "nsIHttpChannelInternal.h"
 #include "nsIURIClassifier.h"
+#include "nsIUrlClassifierUtils.h"
 #include "nsNetCID.h"
+#include "nsNetUtil.h"
 #include "nsPrintfCString.h"
+#include "nsProxyRelease.h"
 #include "nsServiceManagerUtils.h"
-#include "nsNetUtil.h"
+#include "nsUrlClassifierDBService.h"
 
 namespace mozilla {
 namespace net {
 
 namespace {
 
-// When we do blacklist/whitelist classification, from a list of features, we
-// need to aggregate them per URI, because not all the features work with the
-// same channel's URI.
-// This struct contains only the features able to deal with a particular URI.
-// See more in GetFeatureTasks().
-struct FeatureTask {
+// Big picture comment
+// -----------------------------------------------------------------------------
+// nsUrlClassifierDBService::channelClassify() classifies a channel using a set
+// of URL-Classifier features. This method minimizes the number of lookups and
+// URI parsing and this is done using the classes here described.
+//
+// The first class is 'FeatureTask' which is able to retrieve the list of
+// features for this channel using the feature-factory. See
+// UrlClassifierFeatureFactory.
+// For each feature, it creates a FeatureData object, which contains the
+// whitelist and blacklist prefs and tables. The reason why we create
+// FeatureData is because:
+// - features are not thread-safe.
+// - we want to store the state of the classification in the FeatureData
+//   object.
+//
+// It can happen that multiple features share the same tables. In order to do
+// the lookup just once, we have TableData class. When multiple features
+// contain the same table, they have references to the same couple TableData +
+// URIData objects.
+//
+// During the classification, the channel's URIs are fragmented. In order to
+// create these fragments just once, we use the URIData class, which is pointed
+// by TableData classes.
+//
+// The creation of these classes happens on the main-thread. The classification
+// happens on the worker thread.
+
+// URIData
+// -----------------------------------------------------------------------------
+
+// In order to avoid multiple URI parsing, we have this class which contains
+// nsIURI and its fragments.
+class URIData {
+ public:
+  NS_INLINE_DECL_THREADSAFE_REFCOUNTING(URIData);
+
+  static nsresult Create(nsIURI* aURI, nsIURI* aInnermostURI, URIData** aData);
+
+  bool IsEqual(nsIURI* aURI) const;
+
+  const nsTArray<nsCString>& Fragments();
+
+  nsIURI* URI() const;
+
+ private:
+  URIData();
+  ~URIData();
+
   nsCOMPtr<nsIURI> mURI;
-  // Let's use RefPtr<> here, because this needs to be used with methods which
-  // require it.
-  nsTArray<RefPtr<nsIUrlClassifierFeature>> mFeatures;
+  nsCString mURISpec;
+  nsTArray<nsCString> mFragments;
 };
 
-// Features are able to classify particular URIs from a channel. For instance,
-// tracking-annotation feature uses the top-level URI to whitelist the current
-// channel's URI; flash feature always uses the channel's URI.  Because of
-// this, this function aggregates feature per URI in an array of FeatureTask
-// object.
-nsresult GetFeatureTasks(
-    nsIChannel* aChannel,
-    const nsTArray<nsCOMPtr<nsIUrlClassifierFeature>>& aFeatures,
-    nsIUrlClassifierFeature::listType aListType,
-    nsTArray<FeatureTask>& aTasks) {
-  MOZ_ASSERT(!aFeatures.IsEmpty());
+/* static */ nsresult URIData::Create(nsIURI* aURI, nsIURI* aInnermostURI,
+                                      URIData** aData) {
+  MOZ_ASSERT(NS_IsMainThread());
+  MOZ_ASSERT(aURI);
+  MOZ_ASSERT(aInnermostURI);
+
+  RefPtr<URIData> data = new URIData();
+  data->mURI = aURI;
+
+  nsCOMPtr<nsIUrlClassifierUtils> utilsService =
+      do_GetService(NS_URLCLASSIFIERUTILS_CONTRACTID);
+  if (NS_WARN_IF(!utilsService)) {
+    return NS_ERROR_FAILURE;
+  }
+
+  nsresult rv = utilsService->GetKeyForURI(aInnermostURI, data->mURISpec);
+  if (NS_WARN_IF(NS_FAILED(rv))) {
+    return rv;
+  }
+
+  UC_LOG(("URIData::Create[%p] - new URIData created for spec %s", data.get(),
+          data->mURISpec.get()));
+
+  data.forget(aData);
+  return NS_OK;
+}
+
+URIData::URIData() { MOZ_ASSERT(NS_IsMainThread()); }
+
+URIData::~URIData() {
+  NS_ReleaseOnMainThreadSystemGroup("URIData:mURI", mURI.forget());
+}
+
+bool URIData::IsEqual(nsIURI* aURI) const {
+  MOZ_ASSERT(NS_IsMainThread());
+  MOZ_ASSERT(aURI);
+
+  bool isEqual = false;
+  nsresult rv = mURI->Equals(aURI, &isEqual);
+  if (NS_WARN_IF(NS_FAILED(rv))) {
+    return false;
+  }
+
+  return isEqual;
+}
+
+const nsTArray<nsCString>& URIData::Fragments() {
+  MOZ_ASSERT(!NS_IsMainThread());
+
+  if (mFragments.IsEmpty()) {
+    nsresult rv = LookupCache::GetLookupFragments(mURISpec, &mFragments);
+    Unused << NS_WARN_IF(NS_FAILED(rv));
+  }
+
+  return mFragments;
+}
+
+nsIURI* URIData::URI() const {
+  MOZ_ASSERT(NS_IsMainThread());
+  return mURI;
+}
+
+// TableData
+// ----------------------------------------------------------------------------
+
+// In order to avoid multiple lookups on the same table + URI, we have this
+// class.
+class TableData {
+ public:
+  NS_INLINE_DECL_THREADSAFE_REFCOUNTING(TableData);
+
+  enum State {
+    eUnclassified,
+    eNoMatch,
+    eMatch,
+  };
+
+  TableData(URIData* aURIData, const nsACString& aTable);
+
+  nsIURI* URI() const;
+
+  const nsACString& Table() const;
+
+  State MatchState() const;
+
+  bool IsEqual(URIData* aURIData, const nsACString& aTable) const;
+
+  // Returns true if the table classifies the URI. This method must be called
+  // on hte classifier worker thread.
+  bool DoLookup(nsUrlClassifierDBServiceWorker* aWorkerClassifier);
+
+ private:
+  ~TableData();
+
+  RefPtr<URIData> mURIData;
+  State mState;
+
+  nsCString mTable;
+  LookupResultArray mResults;
+};
+
+TableData::TableData(URIData* aURIData, const nsACString& aTable)
+    : mURIData(aURIData), mState(eUnclassified), mTable(aTable) {
+  MOZ_ASSERT(NS_IsMainThread());
+  MOZ_ASSERT(aURIData);
+
+  UC_LOG(("TableData CTOR[%p] - new TableData created %s", this,
+          aTable.BeginReading()));
+}
+
+TableData::~TableData() = default;
 
-  // Let's unify features per nsIURI.
-  for (nsIUrlClassifierFeature* feature : aFeatures) {
-    nsCOMPtr<nsIURI> uri;
-    nsresult rv =
-        feature->GetURIByListType(aChannel, aListType, getter_AddRefs(uri));
-    if (NS_WARN_IF(NS_FAILED(rv)) || !uri) {
-      if (UC_LOG_ENABLED()) {
-        nsAutoCString errorName;
-        GetErrorName(rv, errorName);
-        UC_LOG(
-            ("GetFeatureTasks got an unexpected error (rv=%s) while trying to "
-             "create a whitelist URI. Allowing tracker.",
-             errorName.get()));
-      }
-      return rv;
-    }
+nsIURI* TableData::URI() const {
+  MOZ_ASSERT(NS_IsMainThread());
+  return mURIData->URI();
+}
+
+const nsACString& TableData::Table() const {
+  MOZ_ASSERT(NS_IsMainThread());
+  return mTable;
+}
+
+TableData::State TableData::MatchState() const {
+  MOZ_ASSERT(NS_IsMainThread());
+  return mState;
+}
+
+bool TableData::IsEqual(URIData* aURIData, const nsACString& aTable) const {
+  MOZ_ASSERT(NS_IsMainThread());
+  return mURIData == aURIData && mTable == aTable;
+}
+
+bool TableData::DoLookup(nsUrlClassifierDBServiceWorker* aWorkerClassifier) {
+  MOZ_ASSERT(!NS_IsMainThread());
+  MOZ_ASSERT(aWorkerClassifier);
+
+  if (mState == TableData::eUnclassified) {
+    UC_LOG(("TableData::DoLookup[%p] - starting lookup", this));
 
-    MOZ_ASSERT(uri);
+    const nsTArray<nsCString>& fragments = mURIData->Fragments();
+    nsresult rv = aWorkerClassifier->DoSingleLocalLookupWithURIFragments(
+        fragments, mTable, mResults);
+    Unused << NS_WARN_IF(NS_FAILED(rv));
+
+    mState = mResults.IsEmpty() ? TableData::eNoMatch : TableData::eMatch;
+
+    UC_LOG(("TableData::DoLookup[%p] - lookup completed. Matches: %d", this,
+            (int)mResults.Length()));
+  }
+
+  return !mResults.IsEmpty();
+}
+
+// FeatureData
+// ----------------------------------------------------------------------------
+
+class FeatureTask;
+
+// This is class contains all the Feature data.
+class FeatureData {
+  enum State {
+    eUnclassified,
+    eNoMatch,
+    eMatchBlacklist,
+    eMatchWhitelist,
+  };
 
-    bool found = false;
-    for (FeatureTask& task : aTasks) {
-      bool equal = false;
-      rv = task.mURI->Equals(uri, &equal);
-      if (NS_WARN_IF(NS_FAILED(rv))) {
-        return rv;
-      }
+ public:
+  FeatureData();
+  ~FeatureData();
+
+  nsresult Initialize(FeatureTask* aTask, nsIChannel* aChannel,
+                      nsIUrlClassifierFeature* aFeature);
+
+  void DoLookup(nsUrlClassifierDBServiceWorker* aWorkerClassifier);
+
+  // Returns true if the next feature should be processed.
+  bool MaybeCompleteClassification(nsIChannel* aChannel);
+
+ private:
+  nsresult InitializeList(FeatureTask* aTask, nsIChannel* aChannel,
+                          nsIUrlClassifierFeature::listType aListType,
+                          nsTArray<RefPtr<TableData>>& aList);
+
+  State mState;
+  nsCOMPtr<nsIUrlClassifierFeature> mFeature;
+
+  nsTArray<RefPtr<TableData>> mBlacklistTables;
+  nsTArray<RefPtr<TableData>> mWhitelistTables;
+
+  // blacklist + whitelist.
+  nsCString mHostInPrefTables[2];
+};
+
+FeatureData::FeatureData() : mState(eUnclassified) {}
 
-      if (equal) {
-        task.mFeatures.AppendElement(feature);
-        found = true;
-        break;
-      }
-    }
+FeatureData::~FeatureData() {
+  NS_ReleaseOnMainThreadSystemGroup("FeatureData:mFeature", mFeature.forget());
+}
+
+nsresult FeatureData::Initialize(FeatureTask* aTask, nsIChannel* aChannel,
+                                 nsIUrlClassifierFeature* aFeature) {
+  MOZ_ASSERT(NS_IsMainThread());
+  MOZ_ASSERT(aTask);
+  MOZ_ASSERT(aChannel);
+  MOZ_ASSERT(aFeature);
 
-    if (!found) {
-      FeatureTask* task = aTasks.AppendElement();
-      task->mURI = uri;
-      task->mFeatures.AppendElement(feature);
-    }
+  nsAutoCString featureName;
+  aFeature->GetName(featureName);
+  UC_LOG(("FeatureData::Initialize[%p] - Feature %s - Channel %p", this,
+          featureName.get(), aChannel));
+
+  mFeature = aFeature;
+
+  nsresult rv = InitializeList(
+      aTask, aChannel, nsIUrlClassifierFeature::blacklist, mBlacklistTables);
+  if (NS_WARN_IF(NS_FAILED(rv))) {
+    return rv;
+  }
+
+  rv = InitializeList(aTask, aChannel, nsIUrlClassifierFeature::whitelist,
+                      mWhitelistTables);
+  if (NS_WARN_IF(NS_FAILED(rv))) {
+    return rv;
   }
 
   return NS_OK;
 }
 
-nsresult TrackerFound(
-    const nsTArray<RefPtr<nsIUrlClassifierFeatureResult>>& aResults,
-    nsIChannel* aChannel, const std::function<void()>& aCallback) {
-  // Let's ask the features to do the magic.
-  for (nsIUrlClassifierFeatureResult* result : aResults) {
-    UrlClassifierFeatureResult* r =
-        static_cast<UrlClassifierFeatureResult*>(result);
+void FeatureData::DoLookup(nsUrlClassifierDBServiceWorker* aWorkerClassifier) {
+  MOZ_ASSERT(!NS_IsMainThread());
+  MOZ_ASSERT(aWorkerClassifier);
+  MOZ_ASSERT(mState == eUnclassified);
+
+  UC_LOG(("FeatureData::DoLookup[%p] - lookup starting", this));
+
+  // This is wrong, but it's fast: we don't want to check if the host is in the
+  // blacklist table if we know that it's going to be whitelisted by pref.
+  // So, also if maybe it's not blacklisted, let's consider it 'whitelisted'.
+  if (!mHostInPrefTables[nsIUrlClassifierFeature::whitelist].IsEmpty()) {
+    UC_LOG(("FeatureData::DoLookup[%p] - whitelisted by pref", this));
+    mState = eMatchWhitelist;
+    return;
+  }
+
+  // Let's check if this feature blacklists the URI.
+
+  bool isBlacklisted =
+      !mHostInPrefTables[nsIUrlClassifierFeature::blacklist].IsEmpty();
+
+  UC_LOG(("FeatureData::DoLookup[%p] - blacklisted by pref: %d", this,
+          isBlacklisted));
+
+  if (isBlacklisted == false) {
+    for (TableData* tableData : mBlacklistTables) {
+      if (tableData->DoLookup(aWorkerClassifier)) {
+        isBlacklisted = true;
+      }
+    }
+  }
+
+  UC_LOG(("FeatureData::DoLookup[%p] - blacklisted before whitelisting: %d",
+          this, isBlacklisted));
+
+  if (!isBlacklisted) {
+    mState = eNoMatch;
+    return;
+  }
+
+  // Now, let's check if we need to whitelist the same URI.
+
+  for (TableData* tableData : mWhitelistTables) {
+    // If one of the whitelist table matches the URI, we don't need to continue
+    // with the others: the feature is whitelisted.
+    if (tableData->DoLookup(aWorkerClassifier)) {
+      UC_LOG(("FeatureData::DoLookup[%p] - whitelisted by table", this));
+      mState = eMatchWhitelist;
+      return;
+    }
+  }
+
+  UC_LOG(("FeatureData::DoLookup[%p] - blacklisted", this));
+  mState = eMatchBlacklist;
+}
+
+bool FeatureData::MaybeCompleteClassification(nsIChannel* aChannel) {
+  MOZ_ASSERT(NS_IsMainThread());
+
+  UC_LOG(
+      ("FeatureData::MaybeCompleteClassification[%p] - completing "
+       "classification for channel %p",
+       this, aChannel));
+
+  switch (mState) {
+    case eNoMatch:
+      UC_LOG(
+          ("FeatureData::MaybeCompleteClassification[%p] - no match. Let's "
+           "move on",
+           this));
+      return true;
+
+    case eMatchWhitelist:
+      UC_LOG(
+          ("FeatureData::MaybeCompleteClassification[%p] - whitelisted. Let's "
+           "move on",
+           this));
+      return true;
+
+    case eMatchBlacklist:
+      UC_LOG(
+          ("FeatureData::MaybeCompleteClassification[%p] - blacklisted", this));
+      break;
+
+    case eUnclassified:
+      MOZ_CRASH("We should not be here!");
+      break;
+  }
+
+  MOZ_ASSERT(mState == eMatchBlacklist);
+
+  // Maybe we have to skip this host
+  nsAutoCString skipList;
+  nsresult rv = mFeature->GetSkipHostList(skipList);
+  if (NS_WARN_IF(NS_FAILED(rv))) {
+    UC_LOG(
+        ("FeatureData::MaybeCompleteClassification[%p] - error. Let's move on",
+         this));
+    return true;
+  }
+
+  if (nsContentUtils::IsURIInList(mBlacklistTables[0]->URI(), skipList)) {
+    UC_LOG(
+        ("FeatureData::MaybeCompleteClassification[%p] - uri found in skiplist",
+         this));
+    return true;
+  }
+
+  nsAutoCString list;
+  list.Assign(mHostInPrefTables[nsIUrlClassifierFeature::blacklist]);
+
+  for (TableData* tableData : mBlacklistTables) {
+    if (tableData->MatchState() == TableData::eMatch) {
+      if (!list.IsEmpty()) {
+        list.AppendLiteral(",");
+      }
+
+      list.Append(tableData->Table());
+    }
+  }
+
+  UC_LOG(
+      ("FeatureData::MaybeCompleteClassification[%p] - process channel %p with "
+       "list %s",
+       this, aChannel, list.get()));
+
+  bool shouldContinue = false;
+  rv = mFeature->ProcessChannel(aChannel, list, &shouldContinue);
+  Unused << NS_WARN_IF(NS_FAILED(rv));
+
+  return shouldContinue;
+}
+
+// FeatureTask
+// ----------------------------------------------------------------------------
+
+// A FeatureTask is a class that is able to classify a channel using a set of
+// features. The features are grouped by:
+// - URIs - to avoid extra URI parsing.
+// - Tables - to avoid multiple lookup on the same table.
+class FeatureTask {
+ public:
+  NS_INLINE_DECL_THREADSAFE_REFCOUNTING(FeatureTask);
+
+  static nsresult Create(nsIChannel* aChannel,
+                         std::function<void()>&& aCallback,
+                         FeatureTask** aTask);
+
+  // Called on the classifier thread.
+  void DoLookup(nsUrlClassifierDBServiceWorker* aWorkerClassifier);
+
+  // Called on the main-thread to process the channel.
+  void CompleteClassification();
 
-    bool shouldContinue = false;
-    nsresult rv =
-        r->Feature()->ProcessChannel(aChannel, r->List(), &shouldContinue);
-    // Don't return here! We want to process all the channel and execute the
-    // callback.
-    Unused << NS_WARN_IF(NS_FAILED(rv));
+  nsresult GetOrCreateURIData(nsIURI* aURI, nsIURI* aInnermostURI,
+                              URIData** aData);
+
+  nsresult GetOrCreateTableData(URIData* aURIData, const nsACString& aTable,
+                                TableData** aData);
+
+ private:
+  FeatureTask(nsIChannel* aChannel, std::function<void()>&& aCallback);
+  ~FeatureTask();
+
+  nsCOMPtr<nsIChannel> mChannel;
+  std::function<void()> mCallback;
+
+  nsTArray<FeatureData> mFeatures;
+  nsTArray<RefPtr<URIData>> mURIs;
+  nsTArray<RefPtr<TableData>> mTables;
+};
+
+// Features are able to classify particular URIs from a channel. For instance,
+// tracking-annotation feature uses the top-level URI to whitelist the current
+// channel's URI; flash feature always uses the channel's URI.  Because of
+// this, this function aggregates feature per URI and tables.
+/* static */ nsresult FeatureTask::Create(nsIChannel* aChannel,
+                                          std::function<void()>&& aCallback,
+                                          FeatureTask** aTask) {
+  MOZ_ASSERT(NS_IsMainThread());
+  MOZ_ASSERT(aChannel);
+  MOZ_ASSERT(aTask);
+
+  // We need to obtain the list of nsIUrlClassifierFeature objects able to
+  // classify this channel. If the list is empty, we do an early return.
+  nsTArray<nsCOMPtr<nsIUrlClassifierFeature>> features;
+  UrlClassifierFeatureFactory::GetFeaturesFromChannel(aChannel, features);
+  if (features.IsEmpty()) {
+    UC_LOG(("FeatureTask::Create: Nothing to do for channel %p", aChannel));
+    return NS_ERROR_FAILURE;
+  }
+
+  RefPtr<FeatureTask> task = new FeatureTask(aChannel, std::move(aCallback));
+
+  UC_LOG(("FeatureTask::Create[%p] - FeatureTask created for channel %p",
+          task.get(), aChannel));
+
+  for (nsIUrlClassifierFeature* feature : features) {
+    FeatureData* featureData = task->mFeatures.AppendElement();
+    nsresult rv = featureData->Initialize(task, aChannel, feature);
+    if (NS_WARN_IF(NS_FAILED(rv))) {
+      return rv;
+    }
+  }
+
+  task.forget(aTask);
+  return NS_OK;
+}
+
+FeatureTask::FeatureTask(nsIChannel* aChannel,
+                         std::function<void()>&& aCallback)
+    : mChannel(aChannel), mCallback(std::move(aCallback)) {
+  MOZ_ASSERT(NS_IsMainThread());
+  MOZ_ASSERT(mChannel);
+}
+
+FeatureTask::~FeatureTask() {
+  NS_ReleaseOnMainThreadSystemGroup("FeatureTask::mChannel", mChannel.forget());
+}
+
+nsresult FeatureTask::GetOrCreateURIData(nsIURI* aURI, nsIURI* aInnermostURI,
+                                         URIData** aData) {
+  MOZ_ASSERT(NS_IsMainThread());
+  MOZ_ASSERT(aURI);
+  MOZ_ASSERT(aInnermostURI);
+  MOZ_ASSERT(aData);
+
+  UC_LOG(
+      ("FeatureTask::GetOrCreateURIData[%p] - Checking if a URIData must be "
+       "created",
+       this));
 
-    if (!shouldContinue) {
+  for (URIData* data : mURIs) {
+    if (data->IsEqual(aURI)) {
+      UC_LOG(("FeatureTask::GetOrCreateURIData[%p] - Reuse existing URIData %p",
+              this, data));
+
+      RefPtr<URIData> uriData = data;
+      uriData.forget(aData);
+      return NS_OK;
+    }
+  }
+
+  RefPtr<URIData> data;
+  nsresult rv = URIData::Create(aURI, aInnermostURI, getter_AddRefs(data));
+  if (NS_WARN_IF(NS_FAILED(rv))) {
+    return rv;
+  }
+
+  mURIs.AppendElement(data);
+
+  UC_LOG(("FeatureTask::GetOrCreateURIData[%p] - Create new URIData %p", this,
+          data.get()));
+
+  data.forget(aData);
+  return NS_OK;
+}
+
+nsresult FeatureTask::GetOrCreateTableData(URIData* aURIData,
+                                           const nsACString& aTable,
+                                           TableData** aData) {
+  MOZ_ASSERT(NS_IsMainThread());
+  MOZ_ASSERT(aURIData);
+  MOZ_ASSERT(aData);
+
+  UC_LOG(
+      ("FeatureTask::GetOrCreateTableData[%p] - Checking if TableData must be "
+       "created",
+       this));
+
+  for (TableData* data : mTables) {
+    if (data->IsEqual(aURIData, aTable)) {
+      UC_LOG((
+          "FeatureTask::GetOrCreateTableData[%p] - Reuse existing TableData %p",
+          this, data));
+
+      RefPtr<TableData> tableData = data;
+      tableData.forget(aData);
+      return NS_OK;
+    }
+  }
+
+  RefPtr<TableData> data = new TableData(aURIData, aTable);
+  mTables.AppendElement(data);
+
+  UC_LOG(("FeatureTask::GetOrCreateTableData[%p] - Create new TableData %p",
+          this, data.get()));
+
+  data.forget(aData);
+  return NS_OK;
+}
+
+void FeatureTask::DoLookup(nsUrlClassifierDBServiceWorker* aWorkerClassifier) {
+  MOZ_ASSERT(!NS_IsMainThread());
+  MOZ_ASSERT(aWorkerClassifier);
+
+  UC_LOG(("FeatureTask::DoLookup[%p] - starting lookup", this));
+
+  for (FeatureData& feature : mFeatures) {
+    feature.DoLookup(aWorkerClassifier);
+  }
+
+  UC_LOG(("FeatureTask::DoLookup[%p] - lookup completed", this));
+}
+
+void FeatureTask::CompleteClassification() {
+  MOZ_ASSERT(NS_IsMainThread());
+
+  for (FeatureData& feature : mFeatures) {
+    if (!feature.MaybeCompleteClassification(mChannel)) {
       break;
     }
   }
 
-  aCallback();
-  return NS_OK;
-}
-
-// This class is designed to get the results of checking whitelist.
-class WhitelistClassifierCallback final
-    : public nsIUrlClassifierFeatureCallback {
- public:
-  NS_DECL_THREADSAFE_ISUPPORTS
-  NS_DECL_NSIURLCLASSIFIERFEATURECALLBACK
-
-  WhitelistClassifierCallback(
-      nsIChannel* aChannel,
-      const nsTArray<RefPtr<nsIUrlClassifierFeatureResult>>& aBlacklistResults,
-      std::function<void()>& aCallback)
-      : mChannel(aChannel),
-        mTaskCount(0),
-        mBlacklistResults(aBlacklistResults),
-        mChannelCallback(aCallback) {
-    MOZ_ASSERT(mChannel);
-    MOZ_ASSERT(!mBlacklistResults.IsEmpty());
-  }
-
-  void SetTaskCount(uint32_t aTaskCount) {
-    MOZ_ASSERT(aTaskCount > 0);
-    mTaskCount = aTaskCount;
-  }
-
- private:
-  ~WhitelistClassifierCallback() = default;
-
-  nsresult OnClassifyCompleteInternal();
-
-  nsCOMPtr<nsIChannel> mChannel;
-  nsCOMPtr<nsIURI> mURI;
-  uint32_t mTaskCount;
-  nsTArray<RefPtr<nsIUrlClassifierFeatureResult>> mBlacklistResults;
-  std::function<void()> mChannelCallback;
-
-  nsTArray<RefPtr<nsIUrlClassifierFeatureResult>> mWhitelistResults;
-};
-
-NS_IMPL_ISUPPORTS(WhitelistClassifierCallback, nsIUrlClassifierFeatureCallback)
-
-NS_IMETHODIMP
-WhitelistClassifierCallback::OnClassifyComplete(
-    const nsTArray<RefPtr<nsIUrlClassifierFeatureResult>>& aWhitelistResults) {
-  MOZ_ASSERT(mTaskCount > 0);
-
-  UC_LOG(("WhitelistClassifierCallback[%p]:OnClassifyComplete channel=%p", this,
-          mChannel.get()));
-
-  mWhitelistResults.AppendElements(aWhitelistResults);
-
-  if (--mTaskCount) {
-    // More callbacks will come.
-    return NS_OK;
-  }
-
-  return OnClassifyCompleteInternal();
-}
-
-nsresult WhitelistClassifierCallback::OnClassifyCompleteInternal() {
-  nsTArray<RefPtr<nsIUrlClassifierFeatureResult>> remainingResults;
+  UC_LOG(("FeatureTask::CompleteClassification[%p] - exec callback", this));
 
-  for (nsIUrlClassifierFeatureResult* blacklistResult : mBlacklistResults) {
-    UrlClassifierFeatureResult* result =
-        static_cast<UrlClassifierFeatureResult*>(blacklistResult);
-
-    nsIUrlClassifierFeature* blacklistFeature = result->Feature();
-    MOZ_ASSERT(blacklistFeature);
-
-    bool found = false;
-    for (nsIUrlClassifierFeatureResult* whitelistResult : mWhitelistResults) {
-      // We can do pointer comparison because Features are singletons.
-      if (static_cast<UrlClassifierFeatureResult*>(whitelistResult)
-              ->Feature() == blacklistFeature) {
-        found = true;
-        break;
-      }
-    }
-
-    if (found) {
-      continue;
-    }
-
-    // Maybe we have to skip this host
-    nsAutoCString skipList;
-    nsresult rv = blacklistFeature->GetSkipHostList(skipList);
-    if (NS_WARN_IF(NS_FAILED(rv))) {
-      continue;
-    }
-
-    if (nsContentUtils::IsURIInList(result->URI(), skipList)) {
-      if (UC_LOG_ENABLED()) {
-        UC_LOG(
-            ("WhitelistClassifierCallback[%p]::OnClassifyComplete uri found in "
-             "skiplist",
-             this));
-      }
-
-      continue;
-    }
-
-    remainingResults.AppendElement(blacklistResult);
-  }
-
-  // Whitelist lookup results
-
-  if (remainingResults.IsEmpty()) {
-    if (UC_LOG_ENABLED()) {
-      UC_LOG(
-          ("WhitelistClassifierCallback[%p]::OnClassifyComplete uri fully "
-           "whitelisted",
-           this));
-    }
-
-    mChannelCallback();
-    return NS_OK;
-  }
-
-  if (UC_LOG_ENABLED()) {
-    UC_LOG(
-        ("WhitelistClassifierCallback[%p]::OnClassifyComplete channel[%p] "
-         "should not be whitelisted",
-         this, mChannel.get()));
-  }
-
-  return TrackerFound(remainingResults, mChannel, mChannelCallback);
+  mCallback();
 }
 
-// This class is designed to get the results of checking blacklist.
-class BlacklistClassifierCallback final
-    : public nsIUrlClassifierFeatureCallback {
- public:
-  NS_DECL_THREADSAFE_ISUPPORTS
-  NS_DECL_NSIURLCLASSIFIERFEATURECALLBACK
+nsresult FeatureData::InitializeList(
+    FeatureTask* aTask, nsIChannel* aChannel,
+    nsIUrlClassifierFeature::listType aListType,
+    nsTArray<RefPtr<TableData>>& aList) {
+  MOZ_ASSERT(NS_IsMainThread());
+  MOZ_ASSERT(aTask);
+  MOZ_ASSERT(aChannel);
+
+  UC_LOG(("FeatureData::InitializeList[%p] - Initialize list %d for channel %p",
+          this, aListType, aChannel));
 
-  BlacklistClassifierCallback(nsIChannel* aChannel,
-                              std::function<void()>&& aCallback)
-      : mChannel(aChannel),
-        mTaskCount(0),
-        mChannelCallback(std::move(aCallback)) {
-    MOZ_ASSERT(mChannel);
-  }
-
-  void SetTaskCount(uint32_t aTaskCount) {
-    MOZ_ASSERT(aTaskCount > 0);
-    mTaskCount = aTaskCount;
+  nsCOMPtr<nsIURI> uri;
+  nsresult rv =
+      mFeature->GetURIByListType(aChannel, aListType, getter_AddRefs(uri));
+  if (NS_WARN_IF(NS_FAILED(rv)) || !uri) {
+    if (UC_LOG_ENABLED()) {
+      nsAutoCString errorName;
+      GetErrorName(rv, errorName);
+      UC_LOG(("FeatureData::InitializeList got an unexpected error (rv=%s)",
+              errorName.get()));
+    }
+    return rv;
   }
 
- private:
-  ~BlacklistClassifierCallback() = default;
-
-  nsresult OnClassifyCompleteInternal();
-
-  nsCOMPtr<nsIChannel> mChannel;
-  uint32_t mTaskCount;
-  std::function<void()> mChannelCallback;
-
-  nsTArray<RefPtr<nsIUrlClassifierFeatureResult>> mResults;
-};
-
-NS_IMPL_ISUPPORTS(BlacklistClassifierCallback, nsIUrlClassifierFeatureCallback)
-
-NS_IMETHODIMP
-BlacklistClassifierCallback::OnClassifyComplete(
-    const nsTArray<RefPtr<nsIUrlClassifierFeatureResult>>& aResults) {
-  MOZ_ASSERT(mTaskCount > 0);
-
-  UC_LOG(("BlacklistClassifierCallback[%p]:OnClassifyComplete - remaining %d",
-          this, mTaskCount));
-
-  mResults.AppendElements(aResults);
-
-  if (--mTaskCount) {
-    // More callbacks will come.
-    return NS_OK;
+  nsCOMPtr<nsIURI> innermostURI = NS_GetInnermostURI(uri);
+  if (NS_WARN_IF(!innermostURI)) {
+    return NS_ERROR_FAILURE;
   }
 
-  return OnClassifyCompleteInternal();
-}
-
-nsresult BlacklistClassifierCallback::OnClassifyCompleteInternal() {
-  // All good! The URL has not been classified.
-  if (mResults.IsEmpty()) {
-    if (UC_LOG_ENABLED()) {
-      UC_LOG(
-          ("BlacklistClassifierCallback[%p]::OnClassifyComplete uri not found "
-           "in blacklist",
-           this));
-    }
-
-    mChannelCallback();
-    return NS_OK;
+  nsAutoCString host;
+  rv = innermostURI->GetHost(host);
+  if (NS_WARN_IF(NS_FAILED(rv))) {
+    return rv;
   }
 
-  if (UC_LOG_ENABLED()) {
-    UC_LOG(
-        ("BlacklistClassifierCallback[%p]::OnClassifyComplete uri is in "
-         "blacklist. Start checking whitelist.",
-         this));
-  }
-
-  nsTArray<nsCOMPtr<nsIUrlClassifierFeature>> features;
-  for (nsIUrlClassifierFeatureResult* result : mResults) {
-    features.AppendElement(
-        static_cast<UrlClassifierFeatureResult*>(result)->Feature());
+  bool found = false;
+  nsAutoCString tableName;
+  rv = mFeature->HasHostInPreferences(host, aListType, tableName, &found);
+  if (NS_WARN_IF(NS_FAILED(rv))) {
+    return rv;
   }
 
-  nsTArray<FeatureTask> tasks;
-  nsresult rv = GetFeatureTasks(mChannel, features,
-                                nsIUrlClassifierFeature::whitelist, tasks);
-  if (NS_WARN_IF(NS_FAILED(rv))) {
-    return TrackerFound(mResults, mChannel, mChannelCallback);
+  if (found) {
+    mHostInPrefTables[aListType] = tableName;
   }
 
-  if (tasks.IsEmpty()) {
-    UC_LOG(
-        ("BlacklistClassifierCallback[%p]:OnClassifyComplete could not create "
-         "a whitelist URI. Ignoring whitelist.",
-         this));
-
-    return TrackerFound(mResults, mChannel, mChannelCallback);
+  RefPtr<URIData> uriData;
+  rv = aTask->GetOrCreateURIData(uri, innermostURI, getter_AddRefs(uriData));
+  if (NS_WARN_IF(NS_FAILED(rv))) {
+    return rv;
   }
 
-  RefPtr<WhitelistClassifierCallback> callback =
-      new WhitelistClassifierCallback(mChannel, mResults, mChannelCallback);
-
-  nsCOMPtr<nsIURIClassifier> uriClassifier =
-      do_GetService(NS_URICLASSIFIERSERVICE_CONTRACTID, &rv);
-  NS_ENSURE_SUCCESS(rv, rv);
+  MOZ_ASSERT(uriData);
 
-  uint32_t pendingCallbacks = 0;
-  for (FeatureTask& task : tasks) {
-    rv = uriClassifier->AsyncClassifyLocalWithFeatures(
-        task.mURI, task.mFeatures, nsIUrlClassifierFeature::whitelist,
-        callback);
+  nsTArray<nsCString> tables;
+  rv = mFeature->GetTables(aListType, tables);
+  if (NS_WARN_IF(NS_FAILED(rv))) {
+    return rv;
+  }
 
+  for (const nsCString& table : tables) {
+    RefPtr<TableData> data;
+    rv = aTask->GetOrCreateTableData(uriData, table, getter_AddRefs(data));
     if (NS_WARN_IF(NS_FAILED(rv))) {
-      if (UC_LOG_ENABLED()) {
-        nsAutoCString errorName;
-        GetErrorName(rv, errorName);
-        UC_LOG((
-            "BlacklistClassifierCallback[%p]:OnClassifyComplete Failed "
-            "calling AsyncClassifyLocalWithFeatures with rv=%s. Let's move on.",
-            this, errorName.get()));
-      }
-
-      continue;
+      return rv;
     }
 
-    ++pendingCallbacks;
+    MOZ_ASSERT(data);
+    aList.AppendElement(data);
   }
 
-  // All the AsyncClassifyLocalWithFeatures() calls return error. We do not
-  // expect callbacks.
-  if (pendingCallbacks == 0) {
-    if (UC_LOG_ENABLED()) {
-      UC_LOG(
-          ("BlacklistClassifierCallback[%p]:OnClassifyComplete All "
-           "AsyncClassifyLocalWithFeatures() calls return errors. We cannot "
-           "continue.",
-           this));
-    }
-
-    return TrackerFound(mResults, mChannel, mChannelCallback);
-  }
-
-  // Nothing else do here. Let's wait for the WhitelistClassifierCallback.
-  callback->SetTaskCount(pendingCallbacks);
   return NS_OK;
 }
 
 }  // namespace
 
 /* static */ nsresult AsyncUrlChannelClassifier::CheckChannel(
     nsIChannel* aChannel, std::function<void()>&& aCallback) {
   MOZ_ASSERT(XRE_IsParentProcess());
   MOZ_ASSERT(aChannel);
 
   if (!aCallback) {
     return NS_ERROR_INVALID_ARG;
   }
 
-  // We need to obtain the list of nsIUrlClassifierFeature objects able to
-  // classify this channel. If the list is empty, we do an early return.
-  nsTArray<nsCOMPtr<nsIUrlClassifierFeature>> features;
-  UrlClassifierFeatureFactory::GetFeaturesFromChannel(aChannel, features);
-  if (features.IsEmpty()) {
-    UC_LOG(
-        ("AsyncUrlChannelClassifier: Nothing to do for channel %p", aChannel));
-    return NS_ERROR_FAILURE;
-  }
+  UC_LOG(
+      ("AsyncUrlChannelClassifier::CheckChannel starting the classification "
+       "for channel %p",
+       aChannel));
 
-  nsTArray<FeatureTask> tasks;
-  nsresult rv = GetFeatureTasks(aChannel, features,
-                                nsIUrlClassifierFeature::blacklist, tasks);
-  if (NS_WARN_IF(NS_FAILED(rv)) || tasks.IsEmpty()) {
-    return rv;
-  }
-
-  MOZ_ASSERT(!tasks.IsEmpty());
-
-  nsCOMPtr<nsIURIClassifier> uriClassifier =
-      do_GetService(NS_URICLASSIFIERSERVICE_CONTRACTID, &rv);
-  if (NS_FAILED(rv)) {
+  RefPtr<FeatureTask> task;
+  nsresult rv =
+      FeatureTask::Create(aChannel, std::move(aCallback), getter_AddRefs(task));
+  if (NS_WARN_IF(NS_FAILED(rv))) {
     return rv;
   }
 
-  RefPtr<BlacklistClassifierCallback> callback =
-      new BlacklistClassifierCallback(aChannel, std::move(aCallback));
-
-  uint32_t pendingCallbacks = 0;
-  for (FeatureTask& task : tasks) {
-    if (UC_LOG_ENABLED()) {
-      nsCString spec = task.mURI->GetSpecOrDefault();
-      spec.Truncate(
-          std::min(spec.Length(), UrlClassifierCommon::sMaxSpecLength));
-      UC_LOG(("AsyncUrlChannelClassifier: Checking blacklist for uri=%s\n",
-              spec.get()));
-    }
-
-    rv = uriClassifier->AsyncClassifyLocalWithFeatures(
-        task.mURI, task.mFeatures, nsIUrlClassifierFeature::blacklist,
-        callback);
-    if (NS_WARN_IF(NS_FAILED(rv))) {
-      continue;
-    }
-
-    ++pendingCallbacks;
-  }
-
-  // All the AsyncClassifyLocalWithFeatures() calls return error. We do not
-  // expect callbacks.
-  if (pendingCallbacks == 0) {
+  RefPtr<nsUrlClassifierDBServiceWorker> workerClassifier =
+      nsUrlClassifierDBService::GetWorker();
+  if (NS_WARN_IF(!workerClassifier)) {
     return NS_ERROR_FAILURE;
   }
 
-  callback->SetTaskCount(pendingCallbacks);
-  return NS_OK;
+  nsCOMPtr<nsIRunnable> r = NS_NewRunnableFunction(
+      "AsyncUrlChannelClassifier::CheckChannel",
+      [task, workerClassifier]() -> void {
+        MOZ_ASSERT(!NS_IsMainThread());
+        task->DoLookup(workerClassifier);
+
+        nsCOMPtr<nsIRunnable> r = NS_NewRunnableFunction(
+            "AsyncUrlChannelClassifier::CheckChannel - return",
+            [task]() -> void { task->CompleteClassification(); });
+
+        NS_DispatchToMainThread(r);
+      });
+
+  return nsUrlClassifierDBService::BackgroundThread()->Dispatch(
+      r, NS_DISPATCH_NORMAL);
 }
 
 }  // namespace net
 }  // namespace mozilla
--- a/toolkit/components/url-classifier/nsUrlClassifierDBService.cpp
+++ b/toolkit/components/url-classifier/nsUrlClassifierDBService.cpp
@@ -46,17 +46,16 @@
 #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"
 #include "nsNetUtil.h"
 #include "nsToolkitCompsCID.h"
 #include "nsIClassifiedChannel.h"
 
@@ -165,17 +164,17 @@ class FeatureHolder final {
     // Get the set of fragments based on the url. This is necessary because we
     // only look up at most 5 URLs per aSpec, even if aSpec has more than 5
     // components.
     nsTArray<nsCString> fragments;
     nsresult rv = LookupCache::GetLookupFragments(aSpec, &fragments);
     NS_ENSURE_SUCCESS(rv, rv);
 
     for (TableData* tableData : mTableData) {
-      nsresult rv = aWorker->DoSingleLocalLookupWithURIFragments(
+      rv = aWorker->DoSingleLocalLookupWithURIFragments(
           fragments, tableData->mTable, tableData->mResults);
       if (NS_WARN_IF(NS_FAILED(rv))) {
         return rv;
       }
     }
 
     return NS_OK;
   }
@@ -2507,16 +2506,25 @@ nsIThread* nsUrlClassifierDBService::Bac
   return gDbBackgroundThread;
 }
 
 // static
 bool nsUrlClassifierDBService::ShutdownHasStarted() {
   return gShuttingDownThread;
 }
 
+// static
+nsUrlClassifierDBServiceWorker* nsUrlClassifierDBService::GetWorker() {
+  if (!sUrlClassifierDBService) {
+    return nullptr;
+  }
+
+  return sUrlClassifierDBService->mWorker;
+}
+
 NS_IMETHODIMP
 nsUrlClassifierDBService::AsyncClassifyLocalWithFeatures(
     nsIURI* aURI, const nsTArray<RefPtr<nsIUrlClassifierFeature>>& aFeatures,
     nsIUrlClassifierFeature::listType aListType,
     nsIUrlClassifierFeatureCallback* aCallback) {
   MOZ_ASSERT(NS_IsMainThread());
 
   if (gShuttingDownThread) {
--- a/toolkit/components/url-classifier/nsUrlClassifierDBService.h
+++ b/toolkit/components/url-classifier/nsUrlClassifierDBService.h
@@ -69,32 +69,41 @@
 #define PASSWORD_ALLOW_TABLE_PREF "urlclassifier.passwordAllowTable"
 
 using namespace mozilla::safebrowsing;
 
 class nsUrlClassifierDBServiceWorker;
 class nsIThread;
 class nsIURI;
 class UrlClassifierDBServiceWorkerProxy;
+
 namespace mozilla {
+
 namespace safebrowsing {
 class Classifier;
 class ProtocolParser;
 
 nsresult TablesToResponse(const nsACString& tables);
 
 }  // namespace safebrowsing
+
+namespace net {
+class AsyncUrlChannelClassifier;
+}
+
 }  // namespace mozilla
 
 // This is a proxy class that just creates a background thread and delegates
 // calls to the background thread.
 class nsUrlClassifierDBService final : public nsIUrlClassifierDBService,
                                        public nsIURIClassifier,
                                        public nsIUrlClassifierInfo,
                                        public nsIObserver {
+  friend class mozilla::net::AsyncUrlChannelClassifier;
+
  public:
   // This is thread safe. It throws an exception if the thread is busy.
   nsUrlClassifierDBService();
 
   nsresult Init();
 
   static nsUrlClassifierDBService* GetInstance(nsresult* result);
 
@@ -112,16 +121,20 @@ class nsUrlClassifierDBService final : p
   nsresult CacheCompletions(
       const mozilla::safebrowsing::ConstCacheResultArray& results);
 
   static nsIThread* BackgroundThread();
 
   static bool ShutdownHasStarted();
 
  private:
+  // This method is used only by AsyncUrlChannelClassifier. If you want to use
+  // it, please contact a safebrowsing/URL-Classifier peer.
+  static nsUrlClassifierDBServiceWorker* GetWorker();
+
   const nsTArray<nsCString> kObservedPrefs = {
       NS_LITERAL_CSTRING(CHECK_MALWARE_PREF),
       NS_LITERAL_CSTRING(CHECK_PHISHING_PREF),
       NS_LITERAL_CSTRING(CHECK_BLOCKED_PREF),
       NS_LITERAL_CSTRING(MALWARE_TABLE_PREF),
       NS_LITERAL_CSTRING(PHISH_TABLE_PREF),
       NS_LITERAL_CSTRING(TRACKING_TABLE_PREF),
       NS_LITERAL_CSTRING(TRACKING_TABLE_TEST_ENTRIES_PREF),