Bug 1315386 - Make Safe Browsing code more shutdown-aware. r=gcp. r=francois draft
authorThomas Nguyen <tnguyen@mozilla.com>
Fri, 25 Nov 2016 16:02:37 +0800
changeset 443834 7853379886f6ba8b71cb28160360860886ca050a
parent 443602 bad312aefb42982f492ad2cf36f4c6c3d698f4f7
child 538153 25d5e0aff9b05c62cca3ec8a30feb9be6b5c04f6
push id37106
push usertnguyen@mozilla.com
push dateFri, 25 Nov 2016 08:03:11 +0000
reviewersgcp, francois
bugs1315386
milestone53.0a1
Bug 1315386 - Make Safe Browsing code more shutdown-aware. r=gcp. r=francois MozReview-Commit-ID: ATCVfh5YLZl
toolkit/components/downloads/ApplicationReputation.cpp
toolkit/components/url-classifier/Classifier.cpp
toolkit/components/url-classifier/HashStore.cpp
toolkit/components/url-classifier/LookupCache.cpp
toolkit/components/url-classifier/LookupCacheV4.cpp
toolkit/components/url-classifier/ProtocolParser.cpp
toolkit/components/url-classifier/nsUrlClassifierDBService.cpp
toolkit/components/url-classifier/nsUrlClassifierDBService.h
toolkit/components/url-classifier/nsUrlClassifierHashCompleter.js
--- a/toolkit/components/downloads/ApplicationReputation.cpp
+++ b/toolkit/components/downloads/ApplicationReputation.cpp
@@ -87,23 +87,25 @@ mozilla::LazyLogModule ApplicationReputa
 
 class PendingDBLookup;
 
 // A single use class private to ApplicationReputationService encapsulating an
 // nsIApplicationReputationQuery and an nsIApplicationReputationCallback. Once
 // created by ApplicationReputationService, it is guaranteed to call mCallback.
 // This class is private to ApplicationReputationService.
 class PendingLookup final : public nsIStreamListener,
-                            public nsITimerCallback
+                            public nsITimerCallback,
+                            public nsIObserver
 {
 public:
   NS_DECL_ISUPPORTS
   NS_DECL_NSIREQUESTOBSERVER
   NS_DECL_NSISTREAMLISTENER
   NS_DECL_NSITIMERCALLBACK
+  NS_DECL_NSIOBSERVER
 
   // Constructor and destructor.
   PendingLookup(nsIApplicationReputationQuery* aQuery,
                 nsIApplicationReputationCallback* aCallback);
 
   // Start the lookup. The lookup may have 2 parts: local and remote. In the
   // local lookup, PendingDBLookups are created to query the local allow and
   // blocklists for various URIs associated with this downloaded file. In the
@@ -365,17 +367,18 @@ PendingDBLookup::HandleEvent(const nsACS
          this));
     Accumulate(mozilla::Telemetry::APPLICATION_REPUTATION_LOCAL, NO_LIST);
   }
   return mPendingLookup->LookupNext();
 }
 
 NS_IMPL_ISUPPORTS(PendingLookup,
                   nsIStreamListener,
-                  nsIRequestObserver)
+                  nsIRequestObserver,
+                  nsIObserver)
 
 PendingLookup::PendingLookup(nsIApplicationReputationQuery* aQuery,
                              nsIApplicationReputationCallback* aCallback) :
   mBlocklistCount(0),
   mAllowlistCount(0),
   mQuery(aQuery),
   mCallback(aCallback)
 {
@@ -1317,16 +1320,34 @@ PendingLookup::Notify(nsITimer* aTimer)
   MOZ_ASSERT(aTimer == mTimeoutTimer);
   Accumulate(mozilla::Telemetry::APPLICATION_REPUTATION_REMOTE_LOOKUP_TIMEOUT,
     true);
   mChannel->Cancel(NS_ERROR_NET_TIMEOUT);
   mTimeoutTimer->Cancel();
   return NS_OK;
 }
 
+///////////////////////////////////////////////////////////////////////////////
+// nsIObserver implementation
+NS_IMETHODIMP
+PendingLookup::Observe(nsISupports *aSubject, const char *aTopic,
+                       const char16_t *aData)
+{
+  if (!strcmp(aTopic, "quit-application")) {
+    if (mTimeoutTimer) {
+      mTimeoutTimer->Cancel();
+      mTimeoutTimer = nullptr;
+    }
+    if (mChannel) {
+      mChannel->Cancel(NS_ERROR_ABORT);
+    }
+  }
+  return NS_OK;
+}
+
 ////////////////////////////////////////////////////////////////////////////////
 //// nsIStreamListener
 static nsresult
 AppendSegmentToString(nsIInputStream* inputStream,
                       void *closure,
                       const char *rawSegment,
                       uint32_t toOffset,
                       uint32_t count,
@@ -1514,10 +1535,18 @@ nsresult ApplicationReputationService::Q
   NS_ENSURE_SUCCESS(rv, rv);
   // Bail if the URI hasn't been set.
   NS_ENSURE_STATE(uri);
 
   // Create a new pending lookup and start the call chain.
   RefPtr<PendingLookup> lookup(new PendingLookup(aQuery, aCallback));
   NS_ENSURE_STATE(lookup);
 
+  // Add an observer for shutdown
+  nsCOMPtr<nsIObserverService> observerService =
+    mozilla::services::GetObserverService();
+  if (!observerService) {
+    return NS_ERROR_FAILURE;
+  }
+
+  observerService->AddObserver(lookup, "quit-application", false);
   return lookup->StartLookup();
 }
--- a/toolkit/components/url-classifier/Classifier.cpp
+++ b/toolkit/components/url-classifier/Classifier.cpp
@@ -331,16 +331,21 @@ Classifier::DeleteTables(nsIFile* aDirec
     }
   }
   NS_ENSURE_SUCCESS_VOID(rv);
 }
 
 void
 Classifier::AbortUpdateAndReset(const nsCString& aTable)
 {
+  // We don't need to reset while shutting down. It will only slow us down.
+  if (nsUrlClassifierDBService::ShutdownHasStarted()) {
+    return;
+  }
+
   LOG(("Abort updating table %s.", aTable.get()));
 
   // ResetTables will clear both in-memory & on-disk data.
   ResetTables(Clear_All, nsTArray<nsCString> { aTable });
 
   // Remove the backup and delete directory since we are aborting
   // from an update.
   Unused << RemoveBackupTables();
@@ -929,16 +934,20 @@ Classifier::GetProvider(const nsACString
 
 /*
  * This will consume+delete updates from the passed nsTArray.
 */
 nsresult
 Classifier::UpdateHashStore(nsTArray<TableUpdate*>* aUpdates,
                             const nsACString& aTable)
 {
+  if (nsUrlClassifierDBService::ShutdownHasStarted()) {
+    return NS_ERROR_ABORT;
+  }
+
   LOG(("Classifier::UpdateHashStore(%s)", PromiseFlatCString(aTable).get()));
 
   HashStore store(aTable, GetProvider(aTable), mRootStoreDirectory);
 
   if (!CheckValidUpdate(aUpdates, store.TableName())) {
     return NS_OK;
   }
 
@@ -1027,16 +1036,19 @@ Classifier::UpdateHashStore(nsTArray<Tab
 }
 
 nsresult
 Classifier::UpdateTableV4(nsTArray<TableUpdate*>* aUpdates,
                           const nsACString& aTable)
 {
   MOZ_ASSERT(!NS_IsMainThread(),
              "UpdateTableV4 must be called on the classifier worker thread.");
+  if (nsUrlClassifierDBService::ShutdownHasStarted()) {
+    return NS_ERROR_ABORT;
+  }
 
   LOG(("Classifier::UpdateTableV4(%s)", PromiseFlatCString(aTable).get()));
 
   if (!CheckValidUpdate(aUpdates, aTable)) {
     return NS_OK;
   }
 
   LookupCacheV4* lookupCache =
--- a/toolkit/components/url-classifier/HashStore.cpp
+++ b/toolkit/components/url-classifier/HashStore.cpp
@@ -34,16 +34,17 @@
 #include "nsISeekableStream.h"
 #include "nsIStreamConverterService.h"
 #include "nsNetUtil.h"
 #include "nsCheckSummedOutputStream.h"
 #include "prio.h"
 #include "mozilla/Logging.h"
 #include "zlib.h"
 #include "Classifier.h"
+#include "nsUrlClassifierDBService.h"
 
 // Main store for SafeBrowsing protocol data. We store
 // known add/sub chunks, prefixes and completions in memory
 // during an update, and serialize to disk.
 // We do not store the add prefixes, those are retrieved by
 // decompressing the PrefixSet cache whenever we need to apply
 // an update.
 //
@@ -946,16 +947,19 @@ HashStore::WriteSubPrefixes(nsIOutputStr
 
   return NS_OK;
 }
 
 nsresult
 HashStore::WriteFile()
 {
   NS_ASSERTION(mInUpdate, "Must be in update to write database.");
+  if (nsUrlClassifierDBService::ShutdownHasStarted()) {
+    return NS_ERROR_ABORT;
+  }
 
   nsCOMPtr<nsIFile> storeFile;
   nsresult rv = mStoreDirectory->Clone(getter_AddRefs(storeFile));
   NS_ENSURE_SUCCESS(rv, rv);
   rv = storeFile->AppendNative(mTableName + NS_LITERAL_CSTRING(".sbstore"));
   NS_ENSURE_SUCCESS(rv, rv);
 
   nsCOMPtr<nsIOutputStream> out;
--- a/toolkit/components/url-classifier/LookupCache.cpp
+++ b/toolkit/components/url-classifier/LookupCache.cpp
@@ -139,16 +139,20 @@ LookupCache::DumpCache()
     LOG(("Caches: %s", str.get()));
   }
 }
 #endif
 
 nsresult
 LookupCache::WriteFile()
 {
+  if (nsUrlClassifierDBService::ShutdownHasStarted()) {
+    return NS_ERROR_ABORT;
+  }
+
   nsCOMPtr<nsIFile> psFile;
   nsresult rv = mStoreDirectory->Clone(getter_AddRefs(psFile));
   NS_ENSURE_SUCCESS(rv, rv);
 
   rv = psFile->AppendNative(mTableName + NS_LITERAL_CSTRING(PREFIXSET_SUFFIX));
   NS_ENSURE_SUCCESS(rv, rv);
 
   rv = StoreToFile(psFile);
--- a/toolkit/components/url-classifier/LookupCacheV4.cpp
+++ b/toolkit/components/url-classifier/LookupCacheV4.cpp
@@ -442,16 +442,19 @@ ReadValue(nsIInputStream* aInputStream, 
 
 } // end of unnamed namespace.
 ////////////////////////////////////////////////////////////////////////
 
 nsresult
 LookupCacheV4::WriteMetadata(TableUpdateV4* aTableUpdate)
 {
   NS_ENSURE_ARG_POINTER(aTableUpdate);
+  if (nsUrlClassifierDBService::ShutdownHasStarted()) {
+    return NS_ERROR_ABORT;
+  }
 
   nsCOMPtr<nsIFile> metaFile;
   nsresult rv = mStoreDirectory->Clone(getter_AddRefs(metaFile));
   NS_ENSURE_SUCCESS(rv, rv);
 
   rv = metaFile->AppendNative(mTableName + METADATA_SUFFIX);
   NS_ENSURE_SUCCESS(rv, rv);
 
--- a/toolkit/components/url-classifier/ProtocolParser.cpp
+++ b/toolkit/components/url-classifier/ProtocolParser.cpp
@@ -5,16 +5,17 @@
 
 #include "ProtocolParser.h"
 #include "LookupCache.h"
 #include "nsNetCID.h"
 #include "mozilla/Logging.h"
 #include "prnetdb.h"
 #include "prprf.h"
 
+#include "nsUrlClassifierDBService.h"
 #include "nsUrlClassifierUtils.h"
 #include "nsPrintfCString.h"
 #include "mozilla/Base64.h"
 #include "RiceDeltaDecoder.h"
 #include "mozilla/EndianUtils.h"
 
 // MOZ_LOG=UrlClassifierProtocolParser:5
 mozilla::LazyLogModule gUrlClassifierProtocolParserLog("UrlClassifierProtocolParser");
@@ -139,16 +140,20 @@ ProtocolParserV2::AppendStream(const nsA
   if (NS_FAILED(mUpdateStatus))
     return mUpdateStatus;
 
   nsresult rv;
   mPending.Append(aData);
 
   bool done = false;
   while (!done) {
+    if (nsUrlClassifierDBService::ShutdownHasStarted()) {
+      return NS_ERROR_ABORT;
+    }
+
     if (mState == PROTOCOL_STATE_CONTROL) {
       rv = ProcessControl(&done);
     } else if (mState == PROTOCOL_STATE_CHUNK) {
       rv = ProcessChunk(&done);
     } else {
       NS_ERROR("Unexpected protocol state");
       rv = NS_ERROR_FAILURE;
     }
--- a/toolkit/components/url-classifier/nsUrlClassifierDBService.cpp
+++ b/toolkit/components/url-classifier/nsUrlClassifierDBService.cpp
@@ -188,16 +188,20 @@ nsUrlClassifierDBServiceWorker::QueueLoo
   return NS_OK;
 }
 
 nsresult
 nsUrlClassifierDBServiceWorker::DoLocalLookup(const nsACString& spec,
                                               const nsACString& tables,
                                               LookupResultArray* results)
 {
+  if (gShuttingDownThread) {
+    return NS_ERROR_ABORT;
+  }
+
   MOZ_ASSERT(!NS_IsMainThread(), "DoLocalLookup must be on background thread");
   if (!results) {
     return NS_ERROR_FAILURE;
   }
   // Bail if we haven't been initialized on the background thread.
   if (!mClassifier) {
     return NS_ERROR_NOT_AVAILABLE;
   }
@@ -303,16 +307,20 @@ nsUrlClassifierDBServiceWorker::DoLookup
   c->LookupComplete(completes.forget());
 
   return NS_OK;
 }
 
 nsresult
 nsUrlClassifierDBServiceWorker::HandlePendingLookups()
 {
+  if (gShuttingDownThread) {
+    return NS_ERROR_ABORT;
+  }
+
   MutexAutoLock lock(mPendingLookupLock);
   while (mPendingLookups.Length() > 0) {
     PendingLookup lookup = mPendingLookups[0];
     mPendingLookups.RemoveElementAt(0);
     {
       MutexAutoUnlock unlock(mPendingLookupLock);
       DoLookup(lookup.mKey, lookup.mTables, lookup.mCallback);
     }
@@ -325,16 +333,20 @@ nsUrlClassifierDBServiceWorker::HandlePe
 }
 
 nsresult
 nsUrlClassifierDBServiceWorker::AddNoise(const Prefix aPrefix,
                                          const nsCString tableName,
                                          uint32_t aCount,
                                          LookupResultArray& results)
 {
+  if (gShuttingDownThread) {
+    return NS_ERROR_ABORT;
+  }
+
   if (aCount < 1) {
     return NS_OK;
   }
 
   PrefixArray noiseEntries;
   nsresult rv = mClassifier->ReadNoiseEntries(aPrefix, tableName,
                                               aCount, &noiseEntries);
   NS_ENSURE_SUCCESS(rv, rv);
@@ -354,24 +366,29 @@ nsUrlClassifierDBServiceWorker::AddNoise
 }
 
 // Lookup a key in the db.
 NS_IMETHODIMP
 nsUrlClassifierDBServiceWorker::Lookup(nsIPrincipal* aPrincipal,
                                        const nsACString& aTables,
                                        nsIUrlClassifierCallback* c)
 {
+  if (gShuttingDownThread) {
+    return NS_ERROR_ABORT;
+  }
+
   return HandlePendingLookups();
 }
 
 NS_IMETHODIMP
 nsUrlClassifierDBServiceWorker::GetTables(nsIUrlClassifierCallback* c)
 {
-  if (gShuttingDownThread)
+  if (gShuttingDownThread) {
     return NS_ERROR_NOT_INITIALIZED;
+  }
 
   nsresult rv = OpenDb();
   if (NS_FAILED(rv)) {
     NS_ERROR("Unable to open SafeBrowsing database");
     return NS_ERROR_FAILURE;
   }
 
   NS_ENSURE_SUCCESS(rv, rv);
@@ -435,18 +452,19 @@ nsUrlClassifierDBServiceWorker::BeginUpd
 
 // Called from the stream updater.
 NS_IMETHODIMP
 nsUrlClassifierDBServiceWorker::BeginStream(const nsACString &table)
 {
   LOG(("nsUrlClassifierDBServiceWorker::BeginStream"));
   MOZ_ASSERT(!NS_IsMainThread(), "Streaming must be on the background thread");
 
-  if (gShuttingDownThread)
+  if (gShuttingDownThread) {
     return NS_ERROR_NOT_INITIALIZED;
+  }
 
   NS_ENSURE_STATE(mUpdateObserver);
   NS_ENSURE_STATE(!mInStream);
 
   mInStream = true;
 
   NS_ASSERTION(!mProtocolParser, "Should not have a protocol parser.");
 
@@ -515,18 +533,19 @@ nsUrlClassifierDBServiceWorker::BeginStr
  *
  * Update() can be fed partial data;  It will accumulate data until there is
  * enough to act on.  Finish() should be called when there will be no more
  * data.
  */
 NS_IMETHODIMP
 nsUrlClassifierDBServiceWorker::UpdateStream(const nsACString& chunk)
 {
-  if (gShuttingDownThread)
+  if (gShuttingDownThread) {
     return NS_ERROR_NOT_INITIALIZED;
+  }
 
   NS_ENSURE_STATE(mInStream);
 
   HandlePendingLookups();
 
   // Feed the chunk to the parser.
   return mProtocolParser->AppendStream(chunk);
 }
@@ -582,18 +601,20 @@ nsUrlClassifierDBServiceWorker::FinishSt
   mProtocolParser = nullptr;
 
   return NS_OK;
 }
 
 NS_IMETHODIMP
 nsUrlClassifierDBServiceWorker::FinishUpdate()
 {
-  if (gShuttingDownThread)
+  if (gShuttingDownThread) {
     return NS_ERROR_NOT_INITIALIZED;
+  }
+
   NS_ENSURE_STATE(mUpdateObserver);
 
   if (NS_SUCCEEDED(mUpdateStatus)) {
     mUpdateStatus = ApplyUpdate();
   } else {
     LOG(("nsUrlClassifierDBServiceWorker::FinishUpdate() Not running "
          "ApplyUpdate() since the update has already failed."));
   }
@@ -734,16 +755,20 @@ nsUrlClassifierDBServiceWorker::CloseDb(
   LOG(("urlclassifier db closed\n"));
 
   return NS_OK;
 }
 
 nsresult
 nsUrlClassifierDBServiceWorker::CacheCompletions(CacheResultArray *results)
 {
+  if (gShuttingDownThread) {
+    return NS_ERROR_ABORT;
+  }
+
   LOG(("nsUrlClassifierDBServiceWorker::CacheCompletions [%p]", this));
   if (!mClassifier)
     return NS_OK;
 
   // Ownership is transferred in to us
   nsAutoPtr<CacheResultArray> resultsPtr(results);
 
   if (mLastResults == *resultsPtr) {
@@ -813,16 +838,20 @@ nsUrlClassifierDBServiceWorker::CacheMis
     mMissCache.AppendElement(resultsPtr->ElementAt(i));
   }
   return NS_OK;
 }
 
 nsresult
 nsUrlClassifierDBServiceWorker::OpenDb()
 {
+  if (gShuttingDownThread) {
+    return NS_ERROR_ABORT;
+  }
+
   MOZ_ASSERT(!NS_IsMainThread(), "Must initialize DB on background thread");
   // Connection already open, don't do anything.
   if (mClassifier) {
     return NS_OK;
   }
 
   nsresult rv;
   mCryptoHash = do_CreateInstance(NS_CRYPTO_HASH_CONTRACTID, &rv);
@@ -1342,18 +1371,19 @@ nsUrlClassifierDBService::Init()
   }
 
   // Add an observer for shutdown
   nsCOMPtr<nsIObserverService> observerService =
       mozilla::services::GetObserverService();
   if (!observerService)
     return NS_ERROR_FAILURE;
 
+  // The application is about to quit
+  observerService->AddObserver(this, "quit-application", false);
   observerService->AddObserver(this, "profile-before-change", false);
-  observerService->AddObserver(this, "xpcom-shutdown-threads", false);
 
   // XXX: Do we *really* need to be able to change all of these at runtime?
   // Note: These observers should only be added when everything else above has
   //       succeeded. Failing to do so can cause long shutdown times in certain
   //       situations. See Bug 1247798 and Bug 1244803.
   Preferences::AddStrongObserver(this, CHECK_MALWARE_PREF);
   Preferences::AddStrongObserver(this, CHECK_PHISHING_PREF);
   Preferences::AddStrongObserver(this, CHECK_TRACKING_PREF);
@@ -1463,16 +1493,19 @@ nsUrlClassifierDBService::Classify(nsIPr
 }
 
 NS_IMETHODIMP
 nsUrlClassifierDBService::ClassifyLocalWithTables(nsIURI *aURI,
                                                   const nsACString & aTables,
                                                   nsACString & aTableResults)
 {
   MOZ_ASSERT(NS_IsMainThread(), "ClassifyLocalWithTables must be on main thread");
+  if (gShuttingDownThread) {
+    return NS_ERROR_ABORT;
+  }
 
   if (XRE_IsContentProcess()) {
     using namespace mozilla::dom;
     using namespace mozilla::ipc;
     URIParams uri;
     SerializeURI(aURI, uri);
     nsAutoCString tables(aTables);
     nsAutoCString results;
@@ -1793,36 +1826,47 @@ nsUrlClassifierDBService::Observe(nsISup
       NS_LITERAL_STRING(DOWNLOAD_ALLOW_TABLE_PREF).Equals(aData) ||
       NS_LITERAL_STRING(DISALLOW_COMPLETION_TABLE_PREF).Equals(aData)) {
       // Just read everything again.
       ReadTablesFromPrefs();
     } else if (NS_LITERAL_STRING(CONFIRM_AGE_PREF).Equals(aData)) {
       gFreshnessGuarantee = Preferences::GetInt(CONFIRM_AGE_PREF,
         CONFIRM_AGE_DEFAULT_SEC);
     }
-  } else if (!strcmp(aTopic, "profile-before-change") ||
-             !strcmp(aTopic, "xpcom-shutdown-threads")) {
+  } else if (!strcmp(aTopic, "quit-application")) {
+    Shutdown();
+  } else if (!strcmp(aTopic, "profile-before-change")) {
+    // Unit test does not receive "quit-application",
+    // need call shutdown in this case
     Shutdown();
+    LOG(("joining background thread"));
+    mWorkerProxy = nullptr;
+    nsIThread *backgroundThread = gDbBackgroundThread;
+    gDbBackgroundThread = nullptr;
+    backgroundThread->Shutdown();
+    NS_RELEASE(backgroundThread);
   } else {
     return NS_ERROR_UNEXPECTED;
   }
 
   return NS_OK;
 }
 
 // Join the background thread if it exists.
 nsresult
 nsUrlClassifierDBService::Shutdown()
 {
   LOG(("shutting down db service\n"));
   MOZ_ASSERT(XRE_IsParentProcess());
 
-  if (!gDbBackgroundThread)
+  if (!gDbBackgroundThread || gShuttingDownThread)
     return NS_OK;
 
+  gShuttingDownThread = true;
+
   Telemetry::AutoTimer<Telemetry::URLCLASSIFIER_SHUTDOWN_TIME> timer;
 
   mCompleters.Clear();
 
   nsCOMPtr<nsIPrefBranch> prefs = do_GetService(NS_PREFSERVICE_CONTRACTID);
   if (prefs) {
     prefs->RemoveObserver(CHECK_MALWARE_PREF, this);
     prefs->RemoveObserver(CHECK_PHISHING_PREF, this);
@@ -1844,29 +1888,23 @@ nsUrlClassifierDBService::Shutdown()
   // First close the db connection.
   if (mWorker) {
     rv = mWorkerProxy->CancelUpdate();
     NS_ASSERTION(NS_SUCCEEDED(rv), "failed to post cancel update event");
 
     rv = mWorkerProxy->CloseDb();
     NS_ASSERTION(NS_SUCCEEDED(rv), "failed to post close db event");
   }
-
-  mWorkerProxy = nullptr;
-
-  LOG(("joining background thread"));
-
-  gShuttingDownThread = true;
-
-  nsIThread *backgroundThread = gDbBackgroundThread;
-  gDbBackgroundThread = nullptr;
-  backgroundThread->Shutdown();
-  NS_RELEASE(backgroundThread);
-
   return NS_OK;
 }
 
 nsIThread*
 nsUrlClassifierDBService::BackgroundThread()
 {
   return gDbBackgroundThread;
 }
 
+// static
+bool
+nsUrlClassifierDBService::ShutdownHasStarted()
+{
+  return gShuttingDownThread;
+}
--- a/toolkit/components/url-classifier/nsUrlClassifierDBService.h
+++ b/toolkit/components/url-classifier/nsUrlClassifierDBService.h
@@ -75,16 +75,18 @@ public:
 
   bool GetCompleter(const nsACString& tableName,
                       nsIUrlClassifierHashCompleter** completer);
   nsresult CacheCompletions(mozilla::safebrowsing::CacheResultArray *results);
   nsresult CacheMisses(mozilla::safebrowsing::PrefixArray *results);
 
   static nsIThread* BackgroundThread();
 
+  static bool ShutdownHasStarted();
+
 private:
   // No subclassing
   ~nsUrlClassifierDBService();
 
   // Disallow copy constructor
   nsUrlClassifierDBService(nsUrlClassifierDBService&);
 
   nsresult LookupURI(nsIPrincipal* aPrincipal,
--- a/toolkit/components/url-classifier/nsUrlClassifierHashCompleter.js
+++ b/toolkit/components/url-classifier/nsUrlClassifierHashCompleter.js
@@ -150,20 +150,21 @@ function HashCompleter() {
   // avoid creating multiple requests to the same gethash URL.
   this._currentRequest = null;
   // A map of gethashUrls to HashCompleterRequests that haven't yet begun.
   this._pendingRequests = {};
 
   // A map of gethash URLs to RequestBackoff objects.
   this._backoffs = {};
 
-  // Whether we have been informed of a shutdown by the xpcom-shutdown event.
+  // Whether we have been informed of a shutdown by the shutdown event.
   this._shuttingDown = false;
 
-  Services.obs.addObserver(this, "xpcom-shutdown", true);
+  Services.obs.addObserver(this, "quit-application", false);
+
 }
 
 HashCompleter.prototype = {
   classID: Components.ID("{9111de73-9322-4bfc-8b65-2b727f3e6ec8}"),
   QueryInterface: XPCOMUtils.generateQI([Ci.nsIUrlClassifierHashCompleter,
                                          Ci.nsIRunnable,
                                          Ci.nsIObserver,
                                          Ci.nsISupportsWeakReference,
@@ -254,32 +255,33 @@ HashCompleter.prototype = {
   // Notifies the RequestBackoff of a new request so we can throttle based on
   // max requests/time period. This must be called before a channel is opened,
   // and finishRequest must be called once the response is received.
   noteRequest: function(aGethashUrl) {
     return this._backoffs[aGethashUrl].noteRequest();
   },
 
   observe: function HC_observe(aSubject, aTopic, aData) {
-    if (aTopic == "xpcom-shutdown") {
+    if (aTopic == "quit-application") {
       this._shuttingDown = true;
+      Services.obs.removeObserver(this, "quit-application");
     }
   },
 };
 
 function HashCompleterRequest(aCompleter, aGethashUrl) {
   // HashCompleter object that created this HashCompleterRequest.
   this._completer = aCompleter;
   // The internal set of hashes and callbacks that this request corresponds to.
   this._requests = [];
   // nsIChannel that the hash completion query is transmitted over.
   this._channel = null;
   // Response body of hash completion. Created in onDataAvailable.
   this._response = "";
-  // Whether we have been informed of a shutdown by the xpcom-shutdown event.
+  // Whether we have been informed of a shutdown by the quit-application event.
   this._shuttingDown = false;
   this.gethashUrl = aGethashUrl;
 }
 HashCompleterRequest.prototype = {
   QueryInterface: XPCOMUtils.generateQI([Ci.nsIRequestObserver,
                                          Ci.nsIStreamListener,
                                          Ci.nsIObserver,
                                          Ci.nsISupports]),
@@ -299,17 +301,17 @@ HashCompleterRequest.prototype = {
   // begin.
   begin: function HCR_begin() {
     if (!this._completer.canMakeRequest(this.gethashUrl)) {
       log("Can't make request to " + this.gethashUrl + "\n");
       this.notifyFailure(Cr.NS_ERROR_ABORT);
       return;
     }
 
-    Services.obs.addObserver(this, "xpcom-shutdown", false);
+    Services.obs.addObserver(this, "quit-application", false);
 
     try {
       this.openChannel();
       // Notify the RequestBackoff if opening the channel succeeded. At this
       // point, finishRequest must be called.
       this._completer.noteRequest(this.gethashUrl);
     }
     catch (err) {
@@ -507,17 +509,17 @@ HashCompleterRequest.prototype = {
   },
 
   onStartRequest: function HCR_onStartRequest(aRequest, aContext) {
     // At this point no data is available for us and we have no reason to
     // terminate the connection, so we do nothing until |onStopRequest|.
   },
 
   onStopRequest: function HCR_onStopRequest(aRequest, aContext, aStatusCode) {
-    Services.obs.removeObserver(this, "xpcom-shutdown");
+    Services.obs.removeObserver(this, "quit-application");
 
     if (this._shuttingDown) {
       throw Cr.NS_ERROR_ABORT;
     }
 
     // Default HTTP status to service unavailable, in case we can't retrieve
     // the true status from the channel.
     let httpStatus = 503;
@@ -554,23 +556,23 @@ HashCompleterRequest.prototype = {
     if (success) {
       this.notifySuccess();
     } else {
       this.notifyFailure(aStatusCode);
     }
   },
 
   observe: function HCR_observe(aSubject, aTopic, aData) {
-    if (aTopic != "xpcom-shutdown") {
-      return;
-    }
+    if (aTopic == "quit-application") {
+      this._shuttingDown = true;
+      if (this._channel) {
+        this._channel.cancel(Cr.NS_ERROR_ABORT);
+      }
 
-    this._shuttingDown = true;
-    if (this._channel) {
-      this._channel.cancel(Cr.NS_ERROR_ABORT);
+      Services.obs.removeObserver(this, "quit-application");
     }
   },
 };
 
 // Converts a URL safe base64 string to a normal base64 string. Will not change
 // normal base64 strings. This is modelled after the same function in
 // nsUrlClassifierUtils.h.
 function unUrlsafeBase64(aStr) {