Bug 1356211 - Part 3: Supports custom callback thread for nsIURIClassifier.asyncClassifyLocalWithTables. draft
authorHenry Chang <hchang@mozilla.com>
Tue, 02 May 2017 11:42:01 +0800
changeset 571115 ab3b69fd6222874d91dc458149d883c563d79a5d
parent 571114 cf413b80579442afc202a629ed7cf4305430d7a9
child 626673 41797e3219050f751030179bf3f846fe1e175969
push id56685
push userhchang@mozilla.com
push dateTue, 02 May 2017 03:43:08 +0000
bugs1356211
milestone55.0a1
Bug 1356211 - Part 3: Supports custom callback thread for nsIURIClassifier.asyncClassifyLocalWithTables. With the help of part 1 and 2, we are able to IPC this API off the main thread and do the callback on the specified thread. MozReview-Commit-ID: Atn2FPiQmtV
netwerk/base/nsChannelClassifier.cpp
netwerk/base/nsIURIClassifier.idl
netwerk/protocol/http/nsHttpChannel.cpp
testing/specialpowers/content/specialpowersAPI.js
toolkit/components/url-classifier/nsUrlClassifierDBService.cpp
toolkit/components/url-classifier/nsUrlClassifierDBService.h
--- a/netwerk/base/nsChannelClassifier.cpp
+++ b/netwerk/base/nsChannelClassifier.cpp
@@ -869,17 +869,17 @@ nsChannelClassifier::IsTrackerWhiteliste
   nsCOMPtr<nsIURI> whitelistURI;
   rv = NS_NewURI(getter_AddRefs(whitelistURI), whitelistEntry);
   NS_ENSURE_SUCCESS(rv, rv);
 
   RefPtr<IsTrackerWhitelistedCallback> cb =
     new IsTrackerWhitelistedCallback(this, aList, aProvider, aPrefix,
                                      whitelistEntry);
 
-  return uriClassifier->AsyncClassifyLocalWithTables(whitelistURI, trackingWhitelist, cb);
+  return uriClassifier->AsyncClassifyLocalWithTables(whitelistURI, trackingWhitelist, cb, nullptr);
 }
 
 NS_IMETHODIMP
 nsChannelClassifier::OnClassifyComplete(nsresult aErrorCode,
                                         const nsACString& aList,
                                         const nsACString& aProvider,
                                         const nsACString& aPrefix)
 {
--- a/netwerk/base/nsIURIClassifier.idl
+++ b/netwerk/base/nsIURIClassifier.idl
@@ -9,16 +9,17 @@
 class nsCString;
 %}
 [ref] native StringArrayRef(nsTArray<nsCString>);
 
 interface nsIChannel;
 interface nsIEventTarget;
 interface nsIPrincipal;
 interface nsIURI;
+interface nsIThread;
 
 /**
  * Callback function for nsIURIClassifier lookups.
  */
 [scriptable, function, uuid(8face46e-0c96-470f-af40-0037dcd797bd)]
 interface nsIURIClassifierCallback : nsISupports
 {
   /**
@@ -87,18 +88,22 @@ interface nsIURIClassifier : nsISupports
   [noscript] StringArrayRef classifyLocalWithTables(in nsIURI aURI, in ACString aTables);
   /**
    * 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.)
+   *
+   * Note that if the aCallbackEventTarget is not specified, the callback
+   * will occur on the main thread.
    */
   void asyncClassifyLocalWithTables(in nsIURI aURI,
                                     in ACString aTables,
-                                    in nsIURIClassifierCallback aCallback);
+                                    in nsIURIClassifierCallback aCallback,
+                                    in nsIEventTarget aCallbackEventTarget);
   /**
    * Same as above, but returns a comma separated list of table names.
    * This is an internal interface used only for testing purposes.
    */
   ACString classifyLocal(in nsIURI aURI, in ACString aTables);
 };
--- a/netwerk/protocol/http/nsHttpChannel.cpp
+++ b/netwerk/protocol/http/nsHttpChannel.cpp
@@ -6004,17 +6004,17 @@ nsHttpChannel::InitLocalBlockList(const 
     }
 
     nsAutoCString tables;
     Preferences::GetCString("urlclassifier.trackingTable", &tables);
     nsTArray<nsCString> results;
 
     RefPtr<InitLocalBlockListXpcCallback> xpcCallback
         = new InitLocalBlockListXpcCallback(aCallback);
-    rv = classifier->AsyncClassifyLocalWithTables(uri, tables, xpcCallback);
+    rv = classifier->AsyncClassifyLocalWithTables(uri, tables, xpcCallback, nullptr);
     if (NS_FAILED(rv)) {
         return false;
     }
 
     return true;
 }
 
 NS_IMETHODIMP
--- a/testing/specialpowers/content/specialpowersAPI.js
+++ b/testing/specialpowers/content/specialpowersAPI.js
@@ -2161,16 +2161,17 @@ SpecialPowersAPI.prototype = {
         } else {
           callback.onClassifyComplete.call(undefined, ...args);
         }
       });
     };
 
     return classifierService.asyncClassifyLocalWithTables(unwrapIfWrapped(uri),
                                                           tables,
-                                                          wrapCallback);
+                                                          wrapCallback,
+                                                          null);
   },
 
 };
 
 this.SpecialPowersAPI = SpecialPowersAPI;
 this.bindDOMWindowUtils = bindDOMWindowUtils;
 this.getRawComponents = getRawComponents;
--- a/toolkit/components/url-classifier/nsUrlClassifierDBService.cpp
+++ b/toolkit/components/url-classifier/nsUrlClassifierDBService.cpp
@@ -47,16 +47,18 @@
 #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/ipc/BackgroundChild.h"
+#include "mozilla/ipc/PBackgroundChild.h"
 #include "mozilla/ipc/URIUtils.h"
 #include "nsProxyRelease.h"
 #include "SBTelemetryUtils.h"
 
 namespace mozilla {
 namespace safebrowsing {
 
 nsresult
@@ -1646,16 +1648,19 @@ nsUrlClassifierDBService::Init()
   if (appInfo) {
     bool inSafeMode = false;
     appInfo->GetInSafeMode(&inSafeMode);
     if (inSafeMode) {
       return NS_ERROR_NOT_AVAILABLE;
     }
   }
 
+  NS_NewNamedThread(NS_LITERAL_CSTRING("URL Classifier IPC"),
+                    getter_AddRefs(mPBackgroundIPCThread));
+
   switch (XRE_GetProcessType()) {
   case GeckoProcessType_Default:
     // The parent process is supported.
     break;
   case GeckoProcessType_Content:
     // In a content process, we simply forward all requests to the parent process,
     // so we can skip the initialization steps here.
     // Note that since we never register an observer, Shutdown() will also never
@@ -1834,46 +1839,124 @@ nsUrlClassifierDBService::ClassifyLocal(
     } else {
       aTableResults.AppendLiteral(",");
     }
     aTableResults.Append(result);
   }
   return NS_OK;
 }
 
+mozilla::ipc::PBackgroundChild*
+nsUrlClassifierDBService::EnsurePBackgroundChild()
+{
+  using namespace mozilla::dom;
+  using namespace mozilla::ipc;
+  PBackgroundChild* existingBackgroundChild =
+    BackgroundChild::GetForCurrentThread();
+  // If it's not spun up yet, block until it is, and retry
+  if (!existingBackgroundChild) {
+    LOG(("No existingBackgroundChild"));
+    existingBackgroundChild =
+      BackgroundChild::SynchronouslyCreateForCurrentThread();
+  }
+  return existingBackgroundChild;
+}
+
+namespace {
+
+// This callback wrapper is required when we need to pass the
+// possibly-non-thread-safe callback around. The wrapped callback
+// will be released on the main thread.
+class ThreadSafeURIClassifierCallback : public nsIURIClassifierCallback
+{
+public:
+  NS_DECL_THREADSAFE_ISUPPORTS
+
+  explicit ThreadSafeURIClassifierCallback(nsIURIClassifierCallback* aCallback)
+    : mCallback(aCallback)
+  {
+  }
+
+  NS_IMETHOD
+  OnClassifyComplete(nsresult /*aErrorCode*/,
+                     const nsACString& aLists, // Only this matters.
+                     const nsACString& /*aProvider*/,
+                     const nsACString& /*aPrefix*/) override
+  {
+    return mCallback->OnClassifyComplete(NS_OK, aLists,
+                                         EmptyCString(), EmptyCString());
+  }
+
+private:
+  virtual ~ThreadSafeURIClassifierCallback()
+  {
+    if (!NS_IsMainThread()) {
+      NS_ReleaseOnMainThread(mCallback.forget());
+    }
+  }
+
+  nsCOMPtr<nsIURIClassifierCallback> mCallback;
+};
+
+NS_IMPL_ISUPPORTS(ThreadSafeURIClassifierCallback, nsIURIClassifierCallback)
+
+} // end of unnamed namespace.
+
 NS_IMETHODIMP
 nsUrlClassifierDBService::AsyncClassifyLocalWithTables(nsIURI *aURI,
                                                        const nsACString& aTables,
-                                                       nsIURIClassifierCallback* aCallback)
+                                                       nsIURIClassifierCallback* aCallback,
+                                                       nsIEventTarget* aCallbackEventTarget)
 {
   MOZ_ASSERT(NS_IsMainThread(), "AsyncClassifyLocalWithTables must be called "
                                 "on main thread");
 
+  // Wrap around the raw input for convenience of use.
+  RefPtr<nsIEventTarget> nonNullCallbackEventTarget =
+    aCallbackEventTarget ? aCallbackEventTarget
+                         : mozilla::SystemGroup::EventTargetFor(mozilla::TaskCategory::Other);
+
+  RefPtr<nsIURIClassifierCallback> wrappedCallback =
+    new ThreadSafeURIClassifierCallback(aCallback);
+
   if (XRE_IsContentProcess()) {
     using namespace mozilla::dom;
     using namespace mozilla::ipc;
 
-    ContentChild* content = ContentChild::GetSingleton();
-    MOZ_ASSERT(content);
-
-    auto actor = new URLClassifierLocalChild();
-
-    // TODO: Bug 1353701 - Supports custom event target for labelling.
-    nsCOMPtr<nsIEventTarget> systemGroupEventTarget
-      = mozilla::SystemGroup::EventTargetFor(mozilla::TaskCategory::Other);
-    content->SetEventTargetForActor(actor, systemGroupEventTarget);
-
+    RefPtr<nsUrlClassifierDBService> self = this;
     URIParams uri;
     SerializeURI(aURI, uri);
     nsAutoCString tables(aTables);
-    if (!content->SendPURLClassifierLocalConstructor(actor, uri, tables)) {
-      return NS_ERROR_FAILURE;
-    }
-
-    actor->SetCallback(aCallback);
+
+    // We are gonna dispatch the following runnable to the IPC thread.
+    nsCOMPtr<nsIRunnable> r = NS_NewRunnableFunction([=] () {
+      auto actor = new URLClassifierLocalChild();
+      auto pbackgroundChild = self->EnsurePBackgroundChild();
+      if (!pbackgroundChild) {
+        // We failed to have a PBackgroundChild for IPC. Just callback
+        // with null result on the callback thread.
+        nsCOMPtr<nsIRunnable> cbRunnable = NS_NewRunnableFunction([=] {
+          wrappedCallback->OnClassifyComplete(NS_OK,           // Not used.
+                                              EmptyCString(),
+                                              EmptyCString(),  // provider. (Not used)
+                                              EmptyCString()); // prefix. (Not used)
+        });
+        nonNullCallbackEventTarget->Dispatch(cbRunnable, NS_DISPATCH_NORMAL);
+        return;
+      }
+      actor->SetCallback(wrappedCallback);
+      actor->SetCallbackEventTarget(nonNullCallbackEventTarget);
+      if (!pbackgroundChild->SendPURLClassifierLocalConstructor(actor, uri, tables)) {
+        NS_WARNING("Unable to SendPURLClassifierLocalConstructor()");
+        return;
+      }
+    });
+
+    mPBackgroundIPCThread->Dispatch(r, NS_DISPATCH_NORMAL);
+
     return NS_OK;
   }
 
   if (gShuttingDownThread) {
     return NS_ERROR_ABORT;
   }
 
   using namespace mozilla::Telemetry;
@@ -1887,54 +1970,48 @@ nsUrlClassifierDBService::AsyncClassifyL
   nsCOMPtr<nsIUrlClassifierUtils> utilsService =
     do_GetService(NS_URLCLASSIFIERUTILS_CONTRACTID);
   nsresult rv = utilsService->GetKeyForURI(uri, key);
   NS_ENSURE_SUCCESS(rv, rv);
 
   auto worker = mWorker;
   nsCString tables(aTables);
 
-  // Since aCallback will be passed around threads...
-  nsMainThreadPtrHandle<nsIURIClassifierCallback> callback(
-    new nsMainThreadPtrHolder<nsIURIClassifierCallback>(aCallback));
-
-  nsCOMPtr<nsIRunnable> r =
-    NS_NewRunnableFunction([worker, key, tables, callback, startTime] () -> void {
-
+  nsCOMPtr<nsIRunnable> r = NS_NewRunnableFunction([=] {
+    // Perform the local lookup.
     nsCString matchedLists;
     nsAutoPtr<LookupResultArray> results(new LookupResultArray());
     if (results) {
       nsresult rv = worker->DoLocalLookup(key, tables, results);
       if (NS_SUCCEEDED(rv)) {
         for (uint32_t i = 0; i < results->Length(); i++) {
           if (i > 0) {
             matchedLists.AppendLiteral(",");
           }
           matchedLists.Append(results->ElementAt(i).mTableName);
         }
       }
     }
 
-    nsCOMPtr<nsIRunnable> cbRunnable =
-      NS_NewRunnableFunction([callback, matchedLists, startTime] () -> void {
-        // Measure the time diff between calling and callback.
-        AccumulateDelta_impl<Millisecond>::compute(
-          Telemetry::URLCLASSIFIER_ASYNC_CLASSIFYLOCAL_TIME, startTime);
-
-        // |callback| is captured as const value so ...
-        auto cb = const_cast<nsIURIClassifierCallback*>(callback.get());
-        cb->OnClassifyComplete(NS_OK,           // Not used.
-                               matchedLists,
-                               EmptyCString(),  // provider. (Not used)
-                               EmptyCString()); // prefix. (Not used)
-      });
-
-    NS_DispatchToMainThread(cbRunnable);
+    // We've done the local lookup. Call back on the given callback thread.
+    nsCOMPtr<nsIRunnable> cbRunnable = NS_NewRunnableFunction([=] {
+      // Measure the time diff between calling and callback.
+      AccumulateDelta_impl<Millisecond>::compute(
+        Telemetry::URLCLASSIFIER_ASYNC_CLASSIFYLOCAL_TIME, startTime);
+
+      wrappedCallback->OnClassifyComplete(NS_OK,           // Not used.
+                                          matchedLists,
+                                          EmptyCString(),  // provider. (Not used)
+                                          EmptyCString()); // prefix. (Not used)
+    });
+
+    nonNullCallbackEventTarget->Dispatch(cbRunnable, NS_DISPATCH_NORMAL);
   });
 
+  // Dispatch the lookup task to the worker thread.
   return gDbBackgroundThread->Dispatch(r, NS_DISPATCH_NORMAL);
 }
 
 NS_IMETHODIMP
 nsUrlClassifierDBService::ClassifyLocalWithTables(nsIURI *aURI,
                                                   const nsACString& aTables,
                                                   nsTArray<nsCString>& aTableResults)
 {
@@ -1978,16 +2055,17 @@ nsUrlClassifierDBService::ClassifyLocalW
   }
 
   // In unittests, we may not have been initalized, so don't crash.
   rv = mWorkerProxy->DoLocalLookup(key, aTables, results);
   if (NS_SUCCEEDED(rv)) {
     rv = ProcessLookupResults(results, aTableResults);
     NS_ENSURE_SUCCESS(rv, rv);
   }
+
   return NS_OK;
 }
 
 NS_IMETHODIMP
 nsUrlClassifierDBService::Lookup(nsIPrincipal* aPrincipal,
                                  const nsACString& tables,
                                  nsIUrlClassifierCallback* c)
 {
--- a/toolkit/components/url-classifier/nsUrlClassifierDBService.h
+++ b/toolkit/components/url-classifier/nsUrlClassifierDBService.h
@@ -72,16 +72,21 @@ namespace safebrowsing {
 class Classifier;
 class ProtocolParser;
 class TableUpdate;
 
 nsresult
 TablesToResponse(const nsACString& tables);
 
 } // namespace safebrowsing
+
+namespace ipc {
+  class PBackgroundChild;
+}
+
 } // namespace mozilla
 
 // This is a proxy class that just creates a background thread and delagates
 // calls to the background thread.
 class nsUrlClassifierDBService final : public nsIUrlClassifierDBService,
                                        public nsIURIClassifier,
                                        public nsIObserver
 {
@@ -100,16 +105,18 @@ public:
   NS_DECL_NSIURICLASSIFIER
   NS_DECL_NSIOBSERVER
 
   bool GetCompleter(const nsACString& tableName,
                       nsIUrlClassifierHashCompleter** completer);
   nsresult CacheCompletions(mozilla::safebrowsing::CacheResultArray *results);
   nsresult CacheMisses(mozilla::safebrowsing::PrefixArray *results);
 
+  mozilla::ipc::PBackgroundChild* EnsurePBackgroundChild();
+
   static nsIThread* BackgroundThread();
 
   static bool ShutdownHasStarted();
 
 private:
 
   const nsTArray<nsCString> kObservedPrefs = {
     NS_LITERAL_CSTRING(CHECK_MALWARE_PREF),
@@ -175,16 +182,18 @@ private:
   nsTArray<nsCString> mDisallowCompletionsTables;
 
   // Comma-separated list of tables to use in lookups.
   nsCString mTrackingProtectionTables;
   nsCString mBaseTables;
 
   // Thread that we do the updates on.
   static nsIThread* gDbBackgroundThread;
+
+  nsCOMPtr<nsIThread> mPBackgroundIPCThread;
 };
 
 class nsUrlClassifierDBServiceWorker final : public nsIUrlClassifierDBService
 {
 public:
   nsUrlClassifierDBServiceWorker();
 
   NS_DECL_THREADSAFE_ISUPPORTS