tor-browser

The Tor Browser
git clone https://git.dasho.dev/tor-browser.git
Log | Files | Refs | README | LICENSE

AsyncUrlChannelClassifier.cpp (28863B)


      1 /* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
      2 /* vim:set expandtab ts=4 sw=2 sts=2 cin: */
      3 /* This Source Code Form is subject to the terms of the Mozilla Public
      4 * License, v. 2.0. If a copy of the MPL was not distributed with this
      5 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
      6 
      7 #include "Classifier.h"
      8 #include "HttpBaseChannel.h"
      9 #include "mozilla/Components.h"
     10 #include "mozilla/ErrorNames.h"
     11 #include "mozilla/net/AsyncUrlChannelClassifier.h"
     12 #include "mozilla/dom/BrowsingContext.h"
     13 #include "mozilla/dom/CanonicalBrowsingContext.h"
     14 #include "mozilla/net/UrlClassifierCommon.h"
     15 #include "mozilla/net/UrlClassifierFeatureFactory.h"
     16 #include "mozilla/net/UrlClassifierFeatureResult.h"
     17 #include "nsContentUtils.h"
     18 #include "nsIChannel.h"
     19 #include "nsIHttpChannel.h"
     20 #include "nsIUrlClassifierExceptionList.h"
     21 #include "nsNetCID.h"
     22 #include "nsNetUtil.h"
     23 #include "nsPrintfCString.h"
     24 #include "nsProxyRelease.h"
     25 #include "nsServiceManagerUtils.h"
     26 #include "nsUrlClassifierDBService.h"
     27 #include "nsUrlClassifierUtils.h"
     28 #include "mozilla/net/UrlClassifierCommon.h"
     29 
     30 namespace mozilla {
     31 namespace net {
     32 
     33 namespace {
     34 
     35 // Big picture comment
     36 // -----------------------------------------------------------------------------
     37 // nsUrlClassifierDBService::channelClassify() classifies a channel using a set
     38 // of URL-Classifier features. This method minimizes the number of lookups and
     39 // URI parsing and this is done using the classes here described.
     40 //
     41 // The first class is 'FeatureTask' which is able to retrieve the list of
     42 // features for this channel using the feature-factory. See
     43 // UrlClassifierFeatureFactory.
     44 // For each feature, it creates a FeatureData object, which contains the
     45 // entitylist and blocklist prefs and tables. The reason why we create
     46 // FeatureData is because:
     47 // - features are not thread-safe.
     48 // - we want to store the state of the classification in the FeatureData
     49 //   object.
     50 //
     51 // It can happen that multiple features share the same tables. In order to do
     52 // the lookup just once, we have TableData class. When multiple features
     53 // contain the same table, they have references to the same couple TableData +
     54 // URIData objects.
     55 //
     56 // During the classification, the channel's URIs are fragmented. In order to
     57 // create these fragments just once, we use the URIData class, which is pointed
     58 // by TableData classes.
     59 //
     60 // The creation of these classes happens on the main-thread. The classification
     61 // happens on the worker thread.
     62 
     63 // URIData
     64 // -----------------------------------------------------------------------------
     65 
     66 // In order to avoid multiple URI parsing, we have this class which contains
     67 // nsIURI and its fragments.
     68 class URIData {
     69 public:
     70  NS_INLINE_DECL_THREADSAFE_REFCOUNTING(URIData);
     71 
     72  static nsresult Create(nsIURI* aURI, nsIURI* aInnermostURI,
     73                         nsIUrlClassifierFeature::URIType aURIType,
     74                         URIData** aData);
     75 
     76  bool IsEqual(nsIURI* aURI) const;
     77 
     78  const nsTArray<nsCString>& Fragments();
     79 
     80  nsIURI* URI() const;
     81 
     82 private:
     83  URIData();
     84  ~URIData() = default;
     85 
     86  nsCOMPtr<nsIURI> mURI;
     87  nsCString mURISpec;
     88  nsTArray<nsCString> mFragments;
     89  nsIUrlClassifierFeature::URIType mURIType;
     90 };
     91 
     92 /* static */
     93 nsresult URIData::Create(nsIURI* aURI, nsIURI* aInnermostURI,
     94                         nsIUrlClassifierFeature::URIType aURIType,
     95                         URIData** aData) {
     96  MOZ_ASSERT(NS_IsMainThread());
     97  MOZ_ASSERT(aURI);
     98  MOZ_ASSERT(aInnermostURI);
     99 
    100  RefPtr<URIData> data = new URIData();
    101  data->mURI = aURI;
    102  data->mURIType = aURIType;
    103 
    104  nsUrlClassifierUtils* utilsService = nsUrlClassifierUtils::GetInstance();
    105  if (NS_WARN_IF(!utilsService)) {
    106    return NS_ERROR_FAILURE;
    107  }
    108 
    109  nsresult rv = utilsService->GetKeyForURI(aInnermostURI, data->mURISpec);
    110  if (NS_WARN_IF(NS_FAILED(rv))) {
    111    return rv;
    112  }
    113 
    114  UC_LOG_LEAK(
    115      ("AsyncChannelClassifier::URIData::Create new URIData created for spec "
    116       "%s [this=%p]",
    117       data->mURISpec.get(), data.get()));
    118 
    119  data.forget(aData);
    120  return NS_OK;
    121 }
    122 
    123 URIData::URIData() { MOZ_ASSERT(NS_IsMainThread()); }
    124 
    125 bool URIData::IsEqual(nsIURI* aURI) const {
    126  MOZ_ASSERT(NS_IsMainThread());
    127  MOZ_ASSERT(aURI);
    128 
    129  bool isEqual = false;
    130  nsresult rv = mURI->Equals(aURI, &isEqual);
    131  if (NS_WARN_IF(NS_FAILED(rv))) {
    132    return false;
    133  }
    134 
    135  return isEqual;
    136 }
    137 
    138 const nsTArray<nsCString>& URIData::Fragments() {
    139  MOZ_ASSERT(!NS_IsMainThread());
    140 
    141  if (mFragments.IsEmpty()) {
    142    if (mURIType == nsIUrlClassifierFeature::pairwiseEntitylistURI) {
    143      LookupCache::GetLookupEntitylistFragments(mURISpec, &mFragments);
    144    } else {
    145      LookupCache::GetLookupFragments(mURISpec, &mFragments);
    146    }
    147  }
    148 
    149  return mFragments;
    150 }
    151 
    152 nsIURI* URIData::URI() const {
    153  MOZ_ASSERT(NS_IsMainThread());
    154  return mURI;
    155 }
    156 
    157 // TableData
    158 // ----------------------------------------------------------------------------
    159 
    160 // In order to avoid multiple lookups on the same table + URI, we have this
    161 // class.
    162 class TableData {
    163 public:
    164  NS_INLINE_DECL_THREADSAFE_REFCOUNTING(TableData);
    165 
    166  enum State {
    167    eUnclassified,
    168    eNoMatch,
    169    eMatch,
    170  };
    171 
    172  TableData(URIData* aURIData, const nsACString& aTable);
    173 
    174  nsIURI* URI() const;
    175 
    176  const nsACString& Table() const;
    177 
    178  const LookupResultArray& Result() const;
    179 
    180  State MatchState() const;
    181 
    182  bool IsEqual(URIData* aURIData, const nsACString& aTable) const;
    183 
    184  // Returns true if the table classifies the URI. This method must be called
    185  // on hte classifier worker thread.
    186  bool DoLookup(nsUrlClassifierDBServiceWorker* aWorkerClassifier);
    187 
    188 private:
    189  ~TableData();
    190 
    191  RefPtr<URIData> mURIData;
    192  State mState;
    193 
    194  nsCString mTable;
    195  LookupResultArray mResults;
    196 };
    197 
    198 TableData::TableData(URIData* aURIData, const nsACString& aTable)
    199    : mURIData(aURIData), mState(eUnclassified), mTable(aTable) {
    200  MOZ_ASSERT(NS_IsMainThread());
    201  MOZ_ASSERT(aURIData);
    202 
    203  UC_LOG_LEAK(
    204      ("AsyncChannelClassifier::TableData CTOR - new TableData created %s "
    205       "[this=%p]",
    206       aTable.BeginReading(), this));
    207 }
    208 
    209 TableData::~TableData() = default;
    210 
    211 nsIURI* TableData::URI() const {
    212  MOZ_ASSERT(NS_IsMainThread());
    213  return mURIData->URI();
    214 }
    215 
    216 const nsACString& TableData::Table() const {
    217  MOZ_ASSERT(NS_IsMainThread());
    218  return mTable;
    219 }
    220 
    221 const LookupResultArray& TableData::Result() const {
    222  MOZ_ASSERT(NS_IsMainThread());
    223  return mResults;
    224 }
    225 
    226 TableData::State TableData::MatchState() const {
    227  MOZ_ASSERT(NS_IsMainThread());
    228  return mState;
    229 }
    230 
    231 bool TableData::IsEqual(URIData* aURIData, const nsACString& aTable) const {
    232  MOZ_ASSERT(NS_IsMainThread());
    233  return mURIData == aURIData && mTable == aTable;
    234 }
    235 
    236 bool TableData::DoLookup(nsUrlClassifierDBServiceWorker* aWorkerClassifier) {
    237  MOZ_ASSERT(!NS_IsMainThread());
    238  MOZ_ASSERT(aWorkerClassifier);
    239 
    240  if (mState == TableData::eUnclassified) {
    241    UC_LOG_LEAK(
    242        ("AsyncChannelClassifier::TableData::DoLookup - starting lookup "
    243         "[this=%p]",
    244         this));
    245 
    246    const nsTArray<nsCString>& fragments = mURIData->Fragments();
    247    nsresult rv = aWorkerClassifier->DoSingleLocalLookupWithURIFragments(
    248        fragments, mTable, mResults);
    249    (void)NS_WARN_IF(NS_FAILED(rv));
    250 
    251    mState = mResults.IsEmpty() ? TableData::eNoMatch : TableData::eMatch;
    252 
    253    UC_LOG_LEAK(
    254        ("AsyncChannelClassifier::TableData::DoLookup - lookup completed. "
    255         "Matches: %d [this=%p]",
    256         (int)mResults.Length(), this));
    257  }
    258 
    259  return !mResults.IsEmpty();
    260 }
    261 
    262 // FeatureData
    263 // ----------------------------------------------------------------------------
    264 
    265 class FeatureTask;
    266 
    267 // This is class contains all the Feature data.
    268 class FeatureData {
    269  enum State {
    270    eUnclassified,
    271    eNoMatch,
    272    eMatchBlocklist,
    273    eMatchEntitylist,
    274  };
    275 
    276 public:
    277  FeatureData() = default;
    278  ~FeatureData();
    279 
    280  nsresult Initialize(FeatureTask* aTask, nsIChannel* aChannel,
    281                      nsIUrlClassifierFeature* aFeature);
    282 
    283  void DoLookup(nsUrlClassifierDBServiceWorker* aWorkerClassifier);
    284 
    285  // Returns true if the next feature should be processed.
    286  bool MaybeCompleteClassification(nsIChannel* aChannel);
    287 
    288 private:
    289  nsresult InitializeList(FeatureTask* aTask, nsIChannel* aChannel,
    290                          nsIUrlClassifierFeature::listType aListType,
    291                          nsTArray<RefPtr<TableData>>& aList);
    292 
    293  State mState{eUnclassified};
    294  nsCOMPtr<nsIUrlClassifierFeature> mFeature;
    295 
    296  nsTArray<RefPtr<TableData>> mBlocklistTables;
    297  nsTArray<RefPtr<TableData>> mEntitylistTables;
    298 
    299  // blocklist + entitylist.
    300  nsCString mHostInPrefTables[2];
    301 };
    302 
    303 FeatureData::~FeatureData() {
    304  NS_ReleaseOnMainThread("FeatureData:mFeature", mFeature.forget());
    305 }
    306 
    307 nsresult FeatureData::Initialize(FeatureTask* aTask, nsIChannel* aChannel,
    308                                 nsIUrlClassifierFeature* aFeature) {
    309  MOZ_ASSERT(NS_IsMainThread());
    310  MOZ_ASSERT(aTask);
    311  MOZ_ASSERT(aChannel);
    312  MOZ_ASSERT(aFeature);
    313 
    314  if (UC_LOG_ENABLED()) {
    315    nsAutoCString name;
    316    aFeature->GetName(name);
    317    UC_LOG_LEAK(
    318        ("AsyncChannelClassifier::FeatureData::Initialize - Feature %s "
    319         "[this=%p, channel=%p]",
    320         name.get(), this, aChannel));
    321  }
    322 
    323  mFeature = aFeature;
    324 
    325  nsresult rv = InitializeList(
    326      aTask, aChannel, nsIUrlClassifierFeature::blocklist, mBlocklistTables);
    327  if (NS_WARN_IF(NS_FAILED(rv))) {
    328    return rv;
    329  }
    330 
    331  rv = InitializeList(aTask, aChannel, nsIUrlClassifierFeature::entitylist,
    332                      mEntitylistTables);
    333  if (NS_FAILED(rv)) {
    334    return rv;
    335  }
    336 
    337  return NS_OK;
    338 }
    339 
    340 void FeatureData::DoLookup(nsUrlClassifierDBServiceWorker* aWorkerClassifier) {
    341  MOZ_ASSERT(!NS_IsMainThread());
    342  MOZ_ASSERT(aWorkerClassifier);
    343  MOZ_ASSERT(mState == eUnclassified);
    344 
    345  UC_LOG_LEAK(
    346      ("AsyncChannelClassifier::FeatureData::DoLookup - lookup starting "
    347       "[this=%p]",
    348       this));
    349 
    350  // This is wrong, but it's fast: we don't want to check if the host is in the
    351  // blocklist table if we know that it's going to be entitylisted by pref.
    352  // So, also if maybe it's not blocklisted, let's consider it 'entitylisted'.
    353  if (!mHostInPrefTables[nsIUrlClassifierFeature::entitylist].IsEmpty()) {
    354    UC_LOG_LEAK(
    355        ("AsyncChannelClassifier::FeatureData::DoLookup - entitylisted by pref "
    356         "[this=%p]",
    357         this));
    358    mState = eMatchEntitylist;
    359    return;
    360  }
    361 
    362  // Let's check if this feature blocklists the URI.
    363 
    364  bool isBlocklisted =
    365      !mHostInPrefTables[nsIUrlClassifierFeature::blocklist].IsEmpty();
    366 
    367  UC_LOG_LEAK(
    368      ("AsyncChannelClassifier::FeatureData::DoLookup - blocklisted by pref: "
    369       "%d [this=%p]",
    370       isBlocklisted, this));
    371 
    372  if (!isBlocklisted) {
    373    // If one of the blocklist table matches the URI, we don't need to continue
    374    // with the others: the feature is blocklisted (but maybe also
    375    // entitylisted).
    376    for (TableData* tableData : mBlocklistTables) {
    377      if (tableData->DoLookup(aWorkerClassifier)) {
    378        isBlocklisted = true;
    379        break;
    380      }
    381    }
    382  }
    383 
    384  UC_LOG_LEAK(
    385      ("AsyncChannelClassifier::FeatureData::DoLookup - blocklisted before "
    386       "entitylisting: %d [this=%p]",
    387       isBlocklisted, this));
    388 
    389  if (!isBlocklisted) {
    390    mState = eNoMatch;
    391    return;
    392  }
    393 
    394  // Now, let's check if we need to entitylist the same URI.
    395 
    396  for (TableData* tableData : mEntitylistTables) {
    397    // If one of the entitylist table matches the URI, we don't need to continue
    398    // with the others: the feature is entitylisted.
    399    if (tableData->DoLookup(aWorkerClassifier)) {
    400      UC_LOG_LEAK(
    401          ("AsyncChannelClassifier::FeatureData::DoLookup - entitylisted by "
    402           "table [this=%p]",
    403           this));
    404      mState = eMatchEntitylist;
    405      return;
    406    }
    407  }
    408 
    409  UC_LOG_LEAK(
    410      ("AsyncChannelClassifier::FeatureData::DoLookup - blocklisted [this=%p]",
    411       this));
    412  mState = eMatchBlocklist;
    413 }
    414 
    415 bool FeatureData::MaybeCompleteClassification(nsIChannel* aChannel) {
    416  MOZ_ASSERT(NS_IsMainThread());
    417 
    418  nsAutoCString name;
    419  mFeature->GetName(name);
    420 
    421  UC_LOG_LEAK(
    422      ("AsyncChannelClassifier::FeatureData::MaybeCompleteClassification - "
    423       "completing "
    424       "classification [this=%p channel=%p]",
    425       this, aChannel));
    426 
    427  switch (mState) {
    428    case eNoMatch:
    429      UC_LOG(
    430          ("AsyncChannelClassifier::FeatureData::MaybeCompleteClassification - "
    431           "no match for feature %s. Let's "
    432           "move on [this=%p channel=%p]",
    433           name.get(), this, aChannel));
    434      return true;
    435 
    436    case eMatchEntitylist:
    437      UC_LOG(
    438          ("AsyncChannelClassifier::FeatureData::MayebeCompleteClassification "
    439           "- entitylisted by feature %s. Let's "
    440           "move on [this=%p channel=%p]",
    441           name.get(), this, aChannel));
    442      return true;
    443 
    444    case eMatchBlocklist:
    445      UC_LOG(
    446          ("AsyncChannelClassifier::FeatureData::MaybeCompleteClassification - "
    447           "blocklisted by feature %s [this=%p channel=%p]",
    448           name.get(), this, aChannel));
    449      break;
    450 
    451    case eUnclassified:
    452      MOZ_CRASH("We should not be here!");
    453      break;
    454  }
    455 
    456  MOZ_ASSERT(mState == eMatchBlocklist);
    457 
    458  // Maybe we have to ignore this host
    459  nsCOMPtr<nsIUrlClassifierExceptionList> exceptionList;
    460  nsresult rv = mFeature->GetExceptionList(getter_AddRefs(exceptionList));
    461  if (NS_WARN_IF(NS_FAILED(rv))) {
    462    UC_LOG_WARN(
    463        ("AsyncChannelClassifier::FeatureData::MaybeCompleteClassification - "
    464         "error while getting exception list. Let's move on [exceptionList=%p "
    465         "this=%p channel=%p]",
    466         exceptionList.get(), this, aChannel));
    467    return true;
    468  }
    469 
    470  // Check if current load is allow-listed by the exception list.
    471  if (!mBlocklistTables.IsEmpty() && exceptionList) {
    472    // Get top level URI from channel.
    473    nsCOMPtr<nsIURI> topLevelURI;
    474    rv = UrlClassifierCommon::GetTopWindowURI(aChannel,
    475                                              getter_AddRefs(topLevelURI));
    476    NS_WARNING_ASSERTION(NS_SUCCEEDED(rv), "Failed to get top level URI");
    477 
    478    bool isPrivateBrowsing = NS_UsePrivateBrowsing(aChannel);
    479    bool isAllowListed = false;
    480    rv = exceptionList->Matches(mBlocklistTables[0]->URI(), topLevelURI,
    481                                isPrivateBrowsing, &isAllowListed);
    482    if (NS_SUCCEEDED(rv) && isAllowListed) {
    483      nsCString spec = mBlocklistTables[0]->URI()->GetSpecOrDefault();
    484      spec.Truncate(
    485          std::min(spec.Length(), UrlClassifierCommon::sMaxSpecLength));
    486      UC_LOG(
    487          ("AsyncChannelClassifier::FeatureData::MaybeCompleteClassification - "
    488           "uri %s found in "
    489           "exceptionlist of feature %s [this=%p channel=%p]",
    490           spec.get(), name.get(), this, aChannel));
    491      return true;
    492    }
    493  }
    494 
    495  nsTArray<nsCString> list;
    496  nsTArray<nsCString> hashes;
    497  if (!mHostInPrefTables[nsIUrlClassifierFeature::blocklist].IsEmpty()) {
    498    list.AppendElement(mHostInPrefTables[nsIUrlClassifierFeature::blocklist]);
    499 
    500    // Telemetry expects every tracking channel has hash, create it for test
    501    // entry
    502    Completion complete;
    503    complete.FromPlaintext(
    504        mHostInPrefTables[nsIUrlClassifierFeature::blocklist]);
    505    hashes.AppendElement(complete.ToString());
    506  }
    507 
    508  for (TableData* tableData : mBlocklistTables) {
    509    if (tableData->MatchState() == TableData::eMatch) {
    510      list.AppendElement(tableData->Table());
    511 
    512      for (const auto& r : tableData->Result()) {
    513        hashes.AppendElement(r->hash.complete.ToString());
    514      }
    515    }
    516  }
    517 
    518  UC_LOG_LEAK(
    519      ("AsyncChannelClassifier::FeatureData::MaybeCompleteClassification - "
    520       "process channel [this=%p channel=%p]",
    521       this, aChannel));
    522 
    523  bool shouldContinue = false;
    524  rv = mFeature->ProcessChannel(aChannel, list, hashes, &shouldContinue);
    525  (void)NS_WARN_IF(NS_FAILED(rv));
    526 
    527  return shouldContinue;
    528 }
    529 
    530 // CallbackHolder
    531 // ----------------------------------------------------------------------------
    532 
    533 // This class keeps the callback alive and makes sure that we release it on the
    534 // correct thread.
    535 class CallbackHolder final {
    536 public:
    537  NS_INLINE_DECL_REFCOUNTING(CallbackHolder);
    538 
    539  explicit CallbackHolder(std::function<void()>&& aCallback)
    540      : mCallback(std::move(aCallback)) {}
    541 
    542  void Exec() const { mCallback(); }
    543 
    544 private:
    545  ~CallbackHolder() = default;
    546 
    547  std::function<void()> mCallback;
    548 };
    549 
    550 // FeatureTask
    551 // ----------------------------------------------------------------------------
    552 
    553 // A FeatureTask is a class that is able to classify a channel using a set of
    554 // features. The features are grouped by:
    555 // - URIs - to avoid extra URI parsing.
    556 // - Tables - to avoid multiple lookup on the same table.
    557 class FeatureTask {
    558 public:
    559  NS_INLINE_DECL_THREADSAFE_REFCOUNTING(FeatureTask);
    560 
    561  static nsresult Create(nsIChannel* aChannel,
    562                         std::function<void()>&& aCallback,
    563                         FeatureTask** aTask);
    564 
    565  // Called on the classifier thread.
    566  void DoLookup(nsUrlClassifierDBServiceWorker* aWorkerClassifier);
    567 
    568  // Called on the main-thread to process the channel.
    569  void CompleteClassification();
    570 
    571  nsresult GetOrCreateURIData(nsIURI* aURI, nsIURI* aInnermostURI,
    572                              nsIUrlClassifierFeature::URIType aURIType,
    573                              URIData** aData);
    574 
    575  nsresult GetOrCreateTableData(URIData* aURIData, const nsACString& aTable,
    576                                TableData** aData);
    577 
    578 private:
    579  FeatureTask(nsIChannel* aChannel, std::function<void()>&& aCallback);
    580  ~FeatureTask();
    581 
    582  nsCOMPtr<nsIChannel> mChannel;
    583  RefPtr<CallbackHolder> mCallbackHolder;
    584 
    585  nsTArray<FeatureData> mFeatures;
    586  nsTArray<RefPtr<URIData>> mURIs;
    587  nsTArray<RefPtr<TableData>> mTables;
    588 };
    589 
    590 // Features are able to classify particular URIs from a channel. For instance,
    591 // tracking-annotation feature uses the top-level URI to entitylist the current
    592 // channel's URI.  Because of
    593 // this, this function aggregates feature per URI and tables.
    594 /* static */
    595 nsresult FeatureTask::Create(nsIChannel* aChannel,
    596                             std::function<void()>&& aCallback,
    597                             FeatureTask** aTask) {
    598  MOZ_ASSERT(NS_IsMainThread());
    599  MOZ_ASSERT(aChannel);
    600  MOZ_ASSERT(aTask);
    601 
    602  // We need to obtain the list of nsIUrlClassifierFeature objects able to
    603  // classify this channel. If the list is empty, we do an early return.
    604  nsTArray<nsCOMPtr<nsIUrlClassifierFeature>> features;
    605  UrlClassifierFeatureFactory::GetFeaturesFromChannel(aChannel, features);
    606  if (features.IsEmpty()) {
    607    UC_LOG(
    608        ("AsyncChannelClassifier::FeatureTask::Create - no task is needed for "
    609         "channel %p",
    610         aChannel));
    611    return NS_OK;
    612  }
    613 
    614  RefPtr<FeatureTask> task = new FeatureTask(aChannel, std::move(aCallback));
    615 
    616  UC_LOG(
    617      ("AsyncChannelClassifier::FeatureTask::Create - FeatureTask %p created "
    618       "for channel %p",
    619       task.get(), aChannel));
    620 
    621  for (nsIUrlClassifierFeature* feature : features) {
    622    FeatureData* featureData = task->mFeatures.AppendElement();
    623    nsresult rv = featureData->Initialize(task, aChannel, feature);
    624    if (NS_FAILED(rv)) {
    625      return rv;
    626    }
    627  }
    628 
    629  task.forget(aTask);
    630  return NS_OK;
    631 }
    632 
    633 FeatureTask::FeatureTask(nsIChannel* aChannel,
    634                         std::function<void()>&& aCallback)
    635    : mChannel(aChannel) {
    636  MOZ_ASSERT(NS_IsMainThread());
    637  MOZ_ASSERT(mChannel);
    638 
    639  std::function<void()> callback = std::move(aCallback);
    640  mCallbackHolder = new CallbackHolder(std::move(callback));
    641 }
    642 
    643 FeatureTask::~FeatureTask() {
    644  NS_ReleaseOnMainThread("FeatureTask::mChannel", mChannel.forget());
    645  NS_ReleaseOnMainThread("FeatureTask::mCallbackHolder",
    646                         mCallbackHolder.forget());
    647 }
    648 
    649 nsresult FeatureTask::GetOrCreateURIData(
    650    nsIURI* aURI, nsIURI* aInnermostURI,
    651    nsIUrlClassifierFeature::URIType aURIType, URIData** aData) {
    652  MOZ_ASSERT(NS_IsMainThread());
    653  MOZ_ASSERT(aURI);
    654  MOZ_ASSERT(aInnermostURI);
    655  MOZ_ASSERT(aData);
    656 
    657  UC_LOG_LEAK(
    658      ("AsyncChannelClassifier::FeatureTask::GetOrCreateURIData - checking if "
    659       "a URIData must be "
    660       "created [this=%p]",
    661       this));
    662 
    663  for (URIData* data : mURIs) {
    664    if (data->IsEqual(aURI)) {
    665      UC_LOG_LEAK(
    666          ("AsyncChannelClassifier::FeatureTask::GetOrCreateURIData - reuse "
    667           "existing URIData %p [this=%p]",
    668           data, this));
    669 
    670      RefPtr<URIData> uriData = data;
    671      uriData.forget(aData);
    672      return NS_OK;
    673    }
    674  }
    675 
    676  RefPtr<URIData> data;
    677  nsresult rv =
    678      URIData::Create(aURI, aInnermostURI, aURIType, getter_AddRefs(data));
    679  if (NS_WARN_IF(NS_FAILED(rv))) {
    680    return rv;
    681  }
    682 
    683  mURIs.AppendElement(data);
    684 
    685  UC_LOG_LEAK(
    686      ("AsyncChannelClassifier::FeatureTask::GetOrCreateURIData - create new "
    687       "URIData %p [this=%p]",
    688       data.get(), this));
    689 
    690  data.forget(aData);
    691  return NS_OK;
    692 }
    693 
    694 nsresult FeatureTask::GetOrCreateTableData(URIData* aURIData,
    695                                           const nsACString& aTable,
    696                                           TableData** aData) {
    697  MOZ_ASSERT(NS_IsMainThread());
    698  MOZ_ASSERT(aURIData);
    699  MOZ_ASSERT(aData);
    700 
    701  UC_LOG_LEAK(
    702      ("AsyncChannelClassifier::FeatureTask::GetOrCreateTableData - checking "
    703       "if TableData must be "
    704       "created [this=%p]",
    705       this));
    706 
    707  for (TableData* data : mTables) {
    708    if (data->IsEqual(aURIData, aTable)) {
    709      UC_LOG_LEAK(
    710          ("FeatureTask::GetOrCreateTableData - reuse existing TableData %p "
    711           "[this=%p]",
    712           data, this));
    713 
    714      RefPtr<TableData> tableData = data;
    715      tableData.forget(aData);
    716      return NS_OK;
    717    }
    718  }
    719 
    720  RefPtr<TableData> data = new TableData(aURIData, aTable);
    721  mTables.AppendElement(data);
    722 
    723  UC_LOG_LEAK(
    724      ("AsyncChannelClassifier::FeatureTask::GetOrCreateTableData - create new "
    725       "TableData %p [this=%p]",
    726       data.get(), this));
    727 
    728  data.forget(aData);
    729  return NS_OK;
    730 }
    731 
    732 void FeatureTask::DoLookup(nsUrlClassifierDBServiceWorker* aWorkerClassifier) {
    733  MOZ_ASSERT(!NS_IsMainThread());
    734  MOZ_ASSERT(aWorkerClassifier);
    735 
    736  UC_LOG_LEAK(
    737      ("AsyncChannelClassifier::FeatureTask::DoLookup - starting lookup "
    738       "[this=%p]",
    739       this));
    740 
    741  for (FeatureData& feature : mFeatures) {
    742    feature.DoLookup(aWorkerClassifier);
    743  }
    744 
    745  UC_LOG_LEAK(
    746      ("AsyncChannelClassifier::FeatureTask::DoLookup - lookup completed "
    747       "[this=%p]",
    748       this));
    749 }
    750 
    751 void FeatureTask::CompleteClassification() {
    752  MOZ_ASSERT(NS_IsMainThread());
    753 
    754  for (FeatureData& feature : mFeatures) {
    755    if (!feature.MaybeCompleteClassification(mChannel)) {
    756      break;
    757    }
    758  }
    759 
    760  UC_LOG(
    761      ("AsyncChannelClassifier::FeatureTask::CompleteClassification - complete "
    762       "classification for "
    763       "channel %p [this=%p]",
    764       mChannel.get(), this));
    765 
    766  mCallbackHolder->Exec();
    767 }
    768 
    769 nsresult FeatureData::InitializeList(
    770    FeatureTask* aTask, nsIChannel* aChannel,
    771    nsIUrlClassifierFeature::listType aListType,
    772    nsTArray<RefPtr<TableData>>& aList) {
    773  MOZ_ASSERT(NS_IsMainThread());
    774  MOZ_ASSERT(aTask);
    775  MOZ_ASSERT(aChannel);
    776 
    777  UC_LOG_LEAK(
    778      ("AsyncChannelClassifier::FeatureData::InitializeList - initialize list "
    779       "%d for channel %p [this=%p]",
    780       aListType, aChannel, this));
    781 
    782  nsCOMPtr<nsIURI> uri;
    783  nsIUrlClassifierFeature::URIType URIType;
    784  nsresult rv = mFeature->GetURIByListType(aChannel, aListType, &URIType,
    785                                           getter_AddRefs(uri));
    786  if (NS_FAILED(rv)) {
    787    if (UC_LOG_ENABLED()) {
    788      nsAutoCString errorName;
    789      GetErrorName(rv, errorName);
    790      UC_LOG_LEAK(
    791          ("AsyncChannelClassifier::FeatureData::InitializeList - Got an "
    792           "unexpected error (rv=%s) [this=%p]",
    793           errorName.get(), this));
    794    }
    795    return rv;
    796  }
    797 
    798  if (!uri) {
    799    // Return success when the URI is empty to conitnue to do the lookup.
    800    UC_LOG_LEAK(
    801        ("AsyncChannelClassifier::FeatureData::InitializeList - got an empty "
    802         "URL [this=%p]",
    803         this));
    804    return NS_OK;
    805  }
    806 
    807  nsCOMPtr<nsIURI> innermostURI = NS_GetInnermostURI(uri);
    808  if (NS_WARN_IF(!innermostURI)) {
    809    return NS_ERROR_FAILURE;
    810  }
    811 
    812  nsAutoCString host;
    813  rv = innermostURI->GetHost(host);
    814  if (NS_WARN_IF(NS_FAILED(rv))) {
    815    return rv;
    816  }
    817 
    818  bool found = false;
    819  nsAutoCString tableName;
    820  rv = mFeature->HasHostInPreferences(host, aListType, tableName, &found);
    821  if (NS_WARN_IF(NS_FAILED(rv))) {
    822    return rv;
    823  }
    824 
    825  if (found) {
    826    mHostInPrefTables[aListType] = tableName;
    827  }
    828 
    829  RefPtr<URIData> uriData;
    830  rv = aTask->GetOrCreateURIData(uri, innermostURI, URIType,
    831                                 getter_AddRefs(uriData));
    832  if (NS_WARN_IF(NS_FAILED(rv))) {
    833    return rv;
    834  }
    835 
    836  MOZ_ASSERT(uriData);
    837 
    838  nsTArray<nsCString> tables;
    839  rv = mFeature->GetTables(aListType, tables);
    840  if (NS_WARN_IF(NS_FAILED(rv))) {
    841    return rv;
    842  }
    843 
    844  for (const nsCString& table : tables) {
    845    RefPtr<TableData> data;
    846    rv = aTask->GetOrCreateTableData(uriData, table, getter_AddRefs(data));
    847    if (NS_WARN_IF(NS_FAILED(rv))) {
    848      return rv;
    849    }
    850 
    851    MOZ_ASSERT(data);
    852    aList.AppendElement(data);
    853  }
    854 
    855  return NS_OK;
    856 }
    857 
    858 }  // namespace
    859 
    860 /* static */
    861 void AsyncUrlChannelClassifier::WarmUp() {
    862  // Trigger the construction of the singleton instance.
    863  nsresult rv;
    864  RefPtr<nsUrlClassifierDBService> service =
    865      nsUrlClassifierDBService::GetInstance(&rv);
    866 }
    867 
    868 /* static */
    869 nsresult AsyncUrlChannelClassifier::CheckChannel(
    870    nsIChannel* aChannel, std::function<void()>&& aCallback) {
    871  MOZ_ASSERT(XRE_IsParentProcess());
    872  MOZ_ASSERT(aChannel);
    873 
    874  if (!aCallback) {
    875    return NS_ERROR_INVALID_ARG;
    876  }
    877 
    878  if (UC_LOG_ENABLED()) {
    879    nsCOMPtr<nsIURI> chanURI;
    880    if (NS_SUCCEEDED(aChannel->GetURI(getter_AddRefs(chanURI)))) {
    881      nsCString chanSpec = chanURI->GetSpecOrDefault();
    882      chanSpec.Truncate(
    883          std::min(chanSpec.Length(), UrlClassifierCommon::sMaxSpecLength));
    884 
    885      nsCOMPtr<nsIURI> topWinURI;
    886      (void)UrlClassifierCommon::GetTopWindowURI(aChannel,
    887                                                 getter_AddRefs(topWinURI));
    888      nsCString topWinSpec =
    889          topWinURI ? topWinURI->GetSpecOrDefault() : "(null)"_ns;
    890 
    891      topWinSpec.Truncate(
    892          std::min(topWinSpec.Length(), UrlClassifierCommon::sMaxSpecLength));
    893 
    894      UC_LOG(
    895          ("AsyncUrlChannelClassifier::CheckChannel - starting the "
    896           "classification on channel %p",
    897           aChannel));
    898      UC_LOG(("    uri is %s [channel=%p]", chanSpec.get(), aChannel));
    899      UC_LOG(
    900          ("    top-level uri is %s [channel=%p]", topWinSpec.get(), aChannel));
    901    }
    902  }
    903 
    904  RefPtr<FeatureTask> task;
    905  nsresult rv =
    906      FeatureTask::Create(aChannel, std::move(aCallback), getter_AddRefs(task));
    907  if (NS_FAILED(rv)) {
    908    return rv;
    909  }
    910 
    911  if (!task) {
    912    // No task is needed for this channel, return an error so the caller won't
    913    // wait for a callback.
    914    return NS_ERROR_FAILURE;
    915  }
    916 
    917  RefPtr<nsUrlClassifierDBServiceWorker> workerClassifier =
    918      nsUrlClassifierDBService::GetWorker();
    919  if (NS_WARN_IF(!workerClassifier)) {
    920    return NS_ERROR_FAILURE;
    921  }
    922 
    923  // raise the priority of URLClassifier's return dispatch to the MainThread if
    924  // the channel is considered important
    925  EventQueuePriority eventPriority = EventQueuePriority::Normal;
    926  if (nsCOMPtr<HttpBaseChannel> baseChannel = do_QueryInterface(aChannel)) {
    927    uint32_t classOfServiceFlags = 0;
    928    baseChannel->GetClassFlags(&classOfServiceFlags);
    929    if (classOfServiceFlags &
    930        (nsIClassOfService::Leader | nsIClassOfService::UrgentStart |
    931         nsIClassOfService::Unblocked)) {
    932      eventPriority = EventQueuePriority::MediumHigh;
    933    }
    934  }
    935  if (nsCOMPtr<nsISupportsPriority> supportsPriority =
    936          do_QueryInterface(aChannel)) {
    937    int32_t priority = nsISupportsPriority::PRIORITY_NORMAL;
    938    supportsPriority->GetPriority(&priority);
    939    // note that higher priorities have lower numeric values
    940    if (priority <= nsISupportsPriority::PRIORITY_HIGH) {
    941      eventPriority = EventQueuePriority::MediumHigh;
    942    }
    943  }
    944 
    945  nsCOMPtr<nsIRunnable> r = NS_NewRunnableFunction(
    946      "AsyncUrlChannelClassifier::CheckChannel",
    947      [task, workerClassifier, eventPriority]() -> void {
    948        MOZ_ASSERT(!NS_IsMainThread());
    949        task->DoLookup(workerClassifier);
    950 
    951        NS_DispatchToMainThreadQueue(
    952            NS_NewRunnableFunction(
    953                "AsyncUrlChannelClassifier::CheckChannel - return",
    954                [task]() -> void { task->CompleteClassification(); }),
    955            eventPriority);
    956      });
    957 
    958  // no need to prioritize the dispatch to the URLClassifier thread
    959  // since overriding prioritization is ignored if we aren't on the MainThread
    960  return nsUrlClassifierDBService::BackgroundThread()->Dispatch(
    961      r, NS_DISPATCH_NORMAL);
    962 }
    963 
    964 }  // namespace net
    965 }  // namespace mozilla