/* 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 "ContentClassifierService.h" #include "mozilla/Logging.h" #include "mozilla/net/HttpBaseChannel.h" #include "mozilla/net/ChannelClassifierUtils.h" #include "MainThreadUtils.h" #include "nsDebug.h" #include "mozilla/ContentClassifierEngine.h" #include "mozilla/ClearOnShutdown.h" #include "mozilla/dom/Promise.h" #include "mozilla/Preferences.h" #include "mozilla/Services.h" #include "mozilla/StaticPrefs_privacy.h" #include "mozilla/Components.h" #include "mozilla/MozPromise.h" #include "mozilla/StaticPtr.h" #include "nsIAsyncShutdown.h" #include "nsIChannel.h" #include "nsIStreamLoader.h" #include "nsIURI.h" #include "nsNetUtil.h" #include "nsContentUtils.h" #include "nsIWebProgressListener.h" namespace mozilla { static LazyLogModule gContentClassifierLog("ContentClassifier"); StaticRefPtr ContentClassifierService::sInstance; bool ContentClassifierService::sEnabled = false; namespace { bool HasAnyListNames() { nsAutoCString blockNames; Preferences::GetCString( "privacy.trackingprotection.content.protection.list_names", blockNames); nsAutoCString annotateNames; Preferences::GetCString( "privacy.trackingprotection.content.annotation.list_names", annotateNames); return !blockNames.IsEmpty() || !annotateNames.IsEmpty(); } void NotifyListsLoadedForTesting() { if (!StaticPrefs::privacy_trackingprotection_content_testing()) { return; } nsCOMPtr obs = services::GetObserverService(); if (obs) { obs->NotifyObservers( nullptr, NS_CONTENT_CLASSIFIER_FILTER_LISTS_LOADED_TOPIC, nullptr); } } } // namespace NS_IMPL_ISUPPORTS(ContentClassifierService, nsIAsyncShutdownBlocker, nsIContentClassifierService) ContentClassifierService::ContentClassifierService() : mLock("ContentClassifierService::mLock"), mInitPhase(InitPhase::NotInited) { sEnabled = Preferences::GetBool( "privacy.trackingprotection.content.protection.enabled", false) || Preferences::GetBool( "privacy.trackingprotection.content.annotation.enabled", false); } ContentClassifierService::~ContentClassifierService() = default; // static bool ContentClassifierService::IsEnabled() { if (!sInstance) { return false; } return sEnabled; } // static bool ContentClassifierService::IsInitialized() { if (!sInstance) { return false; } MutexAutoLock lock(sInstance->mLock); return sInstance->mInitPhase == InitPhase::InitSucceeded; } // static void ContentClassifierService::OnPrefChange(const char* aPref, void*) { MOZ_ASSERT(NS_IsMainThread()); // Access sInstance directly rather than GetInstance(), because // GetInstance() returns nullptr when the feature is disabled, but we // need to handle enable/disable transitions here. RefPtr service = sInstance; if (!service) { return; } if (!IsInitialized()) { return; } bool wasEnabled = sEnabled; sEnabled = Preferences::GetBool( "privacy.trackingprotection.content.protection.enabled", false) || Preferences::GetBool( "privacy.trackingprotection.content.annotation.enabled", false); // mRSClient is main-thread only (see header); the NS_IsMainThread // assert at the top of this function covers this read and the // subsequent Init/Shutdown calls. const bool hasRSClient = !!service->mRSClient; if (!wasEnabled && sEnabled && !hasRSClient) { // Feature just became enabled. Start the RS client if list names are set. if (HasAnyListNames()) { service->InitRSClient(); } return; } if (wasEnabled && !sEnabled) { // Feature just became disabled. Tear down the RS client and engines. service->ShutdownRSClient(); return; } // Feature enabled state unchanged. Handle individual pref changes. const nsDependentCString prefStr(aPref); const bool isListNamesPref = prefStr.EqualsLiteral( "privacy.trackingprotection.content.protection.list_names") || prefStr.EqualsLiteral( "privacy.trackingprotection.content.annotation.list_names"); if (isListNamesPref) { if (!sEnabled) { // list_names changed while the feature is disabled. No engines // to rebuild, nothing to fetch; enabling the feature will pick // up the new pref. return; } // Active list names changed. Start RS client if needed, then rebuild // engines from already-stored data to reflect the new selection. if (!hasRSClient && HasAnyListNames()) { service->InitRSClient(); // InitRSClient's async init will rebuild engines once data arrives. return; } { MutexAutoLock lock(service->mLock); service->RebuildEnginesFromStoredData(); } NotifyListsLoadedForTesting(); return; } const bool isTestListUrlsPref = prefStr.EqualsLiteral( "privacy.trackingprotection.content.protection.test_list_urls") || prefStr.EqualsLiteral( "privacy.trackingprotection.content.annotation.test_list_urls"); if (isTestListUrlsPref) { // Test list URLs changed. Reload via the HTTP test path. service->LoadFilterLists(); return; } // An .enabled pref changed but the combined enabled state didn't flip // (e.g. one was already true). Nothing to do - engines are already // populated via whichever path is active. } void ContentClassifierService::Init() { MOZ_ASSERT(XRE_IsParentProcess()); AssertIsOnMainThread(); { MutexAutoLock lock(mLock); if (mInitPhase != InitPhase::NotInited) { return; } MOZ_LOG(gContentClassifierLog, LogLevel::Info, ("ContentClassifierService::Init - initializing")); nsCOMPtr shutdownBarrier = GetAsyncShutdownBarrier(); if (!shutdownBarrier) { mInitPhase = InitPhase::InitFailed; return; } bool closed; nsresult rv = shutdownBarrier->GetIsClosed(&closed); if (NS_FAILED(rv) || closed) { mInitPhase = InitPhase::InitFailed; return; } rv = shutdownBarrier->AddBlocker( this, NS_LITERAL_STRING_FROM_CSTRING(__FILE__), __LINE__, u""_ns); if (NS_FAILED(rv)) { mInitPhase = InitPhase::InitFailed; return; } rv = Preferences::RegisterCallback( &ContentClassifierService::OnPrefChange, "privacy.trackingprotection.content.protection.enabled"_ns); if (NS_FAILED(rv)) { mInitPhase = InitPhase::InitFailed; return; } rv = Preferences::RegisterCallback( &ContentClassifierService::OnPrefChange, "privacy.trackingprotection.content.annotation.enabled"_ns); if (NS_FAILED(rv)) { mInitPhase = InitPhase::InitFailed; return; } rv = Preferences::RegisterCallback( &ContentClassifierService::OnPrefChange, "privacy.trackingprotection.content.protection.test_list_urls"_ns); if (NS_FAILED(rv)) { mInitPhase = InitPhase::InitFailed; return; } rv = Preferences::RegisterCallback( &ContentClassifierService::OnPrefChange, "privacy.trackingprotection.content.annotation.test_list_urls"_ns); if (NS_FAILED(rv)) { mInitPhase = InitPhase::InitFailed; return; } rv = Preferences::RegisterCallback( &ContentClassifierService::OnPrefChange, "privacy.trackingprotection.content.protection.list_names"_ns); if (NS_FAILED(rv)) { mInitPhase = InitPhase::InitFailed; return; } rv = Preferences::RegisterCallback( &ContentClassifierService::OnPrefChange, "privacy.trackingprotection.content.annotation.list_names"_ns); if (NS_FAILED(rv)) { mInitPhase = InitPhase::InitFailed; return; } mInitPhase = InitPhase::InitSucceeded; } // Lock released; safe to call into JS. // Only initialize the RS client if list_names prefs are set, // to avoid interfering with the test-only HTTP loading path. if (sEnabled && HasAnyListNames()) { InitRSClient(); } if (StaticPrefs::privacy_trackingprotection_content_testing()) { LoadFilterLists(); } } void ContentClassifierService::InitRSClient() { MOZ_ASSERT(NS_IsMainThread()); if (mRSClient) { return; } MOZ_LOG_FMT(gContentClassifierLog, LogLevel::Info, "InitRSClient - creating RS client"); nsresult rv; mRSClient = do_GetService(NS_CONTENTCLASSIFIERREMOTESETTINGSCLIENT_CONTRACTID, &rv); if (NS_WARN_IF(NS_FAILED(rv))) { MOZ_LOG_FMT(gContentClassifierLog, LogLevel::Error, "InitRSClient - failed to get RS client service: {:#x}", static_cast(rv)); return; } // The returned Promise is ignored: C++ doesn't need to await the // initial import. Callers that do (such as tests) observe the // NS_CONTENT_CLASSIFIER_FILTER_LISTS_LOADED_TOPIC notification. RefPtr unused; rv = mRSClient->Init(this, getter_AddRefs(unused)); if (NS_WARN_IF(NS_FAILED(rv))) { MOZ_LOG_FMT(gContentClassifierLog, LogLevel::Error, "InitRSClient - failed to init RS client: {:#x}", static_cast(rv)); mRSClient = nullptr; return; } } void ContentClassifierService::ShutdownRSClient() { MOZ_ASSERT(NS_IsMainThread()); MOZ_LOG_FMT(gContentClassifierLog, LogLevel::Info, "ShutdownRSClient"); if (mRSClient) { // Release mRSClient before reacquiring mLock. The JS Shutdown() // implementation does not call back into us, but drop the strong // reference first to be defensive. nsCOMPtr client = std::move(mRSClient); client->Shutdown(); } MutexAutoLock lock(mLock); mFilterListData.Clear(); mBlockEngines.Clear(); mAnnotateEngines.Clear(); } // static already_AddRefed ContentClassifierService::GetInstance() { if (!sInstance) { sInstance = new ContentClassifierService(); ClearOnShutdown(&sInstance); sInstance->Init(); } if (!IsInitialized() || !IsEnabled()) { return nullptr; } return do_AddRef(sInstance); } already_AddRefed ContentClassifierService::GetAsyncShutdownBarrier() const { nsCOMPtr svc = components::AsyncShutdown::Service(); MOZ_RELEASE_ASSERT(svc); nsCOMPtr client; nsresult rv = svc->GetProfileBeforeChange(getter_AddRefs(client)); MOZ_RELEASE_ASSERT(NS_SUCCEEDED(rv)); MOZ_RELEASE_ASSERT(client); return client.forget(); } NS_IMETHODIMP ContentClassifierService::BlockShutdown( nsIAsyncShutdownClient* aClient) { MOZ_ASSERT(NS_IsMainThread()); MOZ_LOG(gContentClassifierLog, LogLevel::Info, ("ContentClassifierService::BlockShutdown - shutting down")); // ShutdownRSClient clears the filter list data and engines. It also // tears down the RS client if one was created (the HTTP-only test // path leaves mRSClient null). ShutdownRSClient(); MutexAutoLock lock(mLock); mInitPhase = InitPhase::ShutdownStarted; Preferences::UnregisterCallback( &ContentClassifierService::OnPrefChange, "privacy.trackingprotection.content.protection.enabled"_ns); Preferences::UnregisterCallback( &ContentClassifierService::OnPrefChange, "privacy.trackingprotection.content.annotation.enabled"_ns); Preferences::UnregisterCallback( &ContentClassifierService::OnPrefChange, "privacy.trackingprotection.content.protection.test_list_urls"_ns); Preferences::UnregisterCallback( &ContentClassifierService::OnPrefChange, "privacy.trackingprotection.content.annotation.test_list_urls"_ns); Preferences::UnregisterCallback( &ContentClassifierService::OnPrefChange, "privacy.trackingprotection.content.protection.list_names"_ns); Preferences::UnregisterCallback( &ContentClassifierService::OnPrefChange, "privacy.trackingprotection.content.annotation.list_names"_ns); content_classifier_teardown_domain_resolver(); RemoveBlocker(); return NS_OK; } void ContentClassifierService::RemoveBlocker() { MOZ_ASSERT(NS_IsMainThread()); mLock.AssertCurrentThreadOwns(); nsCOMPtr asc = GetAsyncShutdownBarrier(); MOZ_ASSERT(asc); DebugOnly rv = asc->RemoveBlocker(this); MOZ_ASSERT(NS_SUCCEEDED(rv)); mInitPhase = InitPhase::ShutdownEnded; } ContentClassifierResult ContentClassifierService::ClassifyWithEngines( const nsTArray>& aEngines, const ContentClassifierRequest& aRequest) { MOZ_ASSERT(!NS_IsMainThread()); mLock.AssertCurrentThreadOwns(); if (mInitPhase != InitPhase::InitSucceeded) { return ContentClassifierResult(NS_ERROR_NOT_INITIALIZED); } if (!aRequest.Valid()) { return ContentClassifierResult(NS_ERROR_INVALID_ARG); } ContentClassifierResult result(NS_OK); for (const auto& engine : aEngines) { ContentClassifierResult thisResult = engine->CheckNetworkRequest(aRequest); result.Accumulate(thisResult); if (result.Important()) { break; } } return result; } NS_IMETHODIMP ContentClassifierService::GetName(nsAString& aName) { aName.AssignLiteral("ContentClassifierService: Shutting down"); return NS_OK; } NS_IMETHODIMP ContentClassifierService::GetState(nsIPropertyBag** aState) { *aState = nullptr; return NS_OK; } ContentClassifierResult ContentClassifierService::ClassifyForAnnotate( const ContentClassifierRequest& aRequest) { MutexAutoLock lock(mLock); ContentClassifierResult result = ClassifyWithEngines(mAnnotateEngines, aRequest); MOZ_LOG(gContentClassifierLog, LogLevel::Debug, ("ClassifyForAnnotate - url=%s hit=%d exception=%d", aRequest.Url().get(), result.Hit(), result.Exception())); return result; } ContentClassifierResult ContentClassifierService::ClassifyForCancel( const ContentClassifierRequest& aRequest) { MutexAutoLock lock(mLock); ContentClassifierResult result = ClassifyWithEngines(mBlockEngines, aRequest); MOZ_LOG(gContentClassifierLog, LogLevel::Debug, ("ClassifyForCancel - url=%s hit=%d exception=%d", aRequest.Url().get(), result.Hit(), result.Exception())); return result; } void ContentClassifierService::AnnotateChannel(nsIChannel* aChannel) { NS_ENSURE_TRUE_VOID(aChannel); nsCOMPtr uri; aChannel->GetURI(getter_AddRefs(uri)); if (uri) { MOZ_LOG(gContentClassifierLog, LogLevel::Debug, ("AnnotateChannel - url=%s", uri->GetSpecOrDefault().get())); } net::ChannelClassifierUtils::AnnotateChannel( aChannel, nsIClassifiedChannel::ClassificationFlags::CLASSIFIED_TRACKING, nsIWebProgressListener::STATE_LOADED_LEVEL_2_TRACKING_CONTENT); } net::ChannelBlockDecision ContentClassifierService::MaybeCancelChannel( nsIChannel* aChannel) { NS_ENSURE_TRUE(aChannel, net::ChannelBlockDecision::Allowed); nsCOMPtr uri; aChannel->GetURI(getter_AddRefs(uri)); if (uri) { MOZ_LOG(gContentClassifierLog, LogLevel::Debug, ("MaybeCancelChannel - url=%s", uri->GetSpecOrDefault().get())); } if (net::ChannelClassifierUtils::IsAllowListed(aChannel)) { return net::ChannelBlockDecision::Allowed; } net::ChannelBlockDecision decision = net::ChannelBlockDecision::Allowed; net::ChannelClassifierUtils::MaybeBlockChannel( aChannel, "content-classifier"_ns, "content-classifier-block"_ns, NS_ERROR_TRACKING_URI, nsIWebProgressListener::STATE_REPLACED_TRACKING_CONTENT, nsIWebProgressListener::STATE_ALLOWED_TRACKING_CONTENT, &decision); return decision; } // nsIContentClassifierService NS_IMETHODIMP ContentClassifierService::SetFilterListData( const nsACString& aName, const nsTArray& aData) { MOZ_ASSERT(NS_IsMainThread()); MOZ_LOG_FMT(gContentClassifierLog, LogLevel::Debug, "SetFilterListData - name={} size={}", aName, aData.Length()); MutexAutoLock lock(mLock); if (mInitPhase != InitPhase::InitSucceeded) { return NS_ERROR_NOT_INITIALIZED; } mFilterListData.InsertOrUpdate(nsCString(aName), aData.Clone()); return NS_OK; } NS_IMETHODIMP ContentClassifierService::RemoveFilterList( const nsACString& aName) { MOZ_ASSERT(NS_IsMainThread()); MOZ_LOG_FMT(gContentClassifierLog, LogLevel::Debug, "RemoveFilterList - name={}", aName); MutexAutoLock lock(mLock); if (mInitPhase != InitPhase::InitSucceeded) { return NS_ERROR_NOT_INITIALIZED; } mFilterListData.Remove(nsCString(aName)); return NS_OK; } NS_IMETHODIMP ContentClassifierService::ApplyFilterLists() { MOZ_ASSERT(NS_IsMainThread()); MOZ_LOG_FMT(gContentClassifierLog, LogLevel::Info, "ApplyFilterLists - rebuilding engines from stored data"); { MutexAutoLock lock(mLock); if (mInitPhase != InitPhase::InitSucceeded) { return NS_ERROR_NOT_INITIALIZED; } RebuildEnginesFromStoredData(); } NotifyListsLoadedForTesting(); return NS_OK; } // Parses a byte buffer of adblock-format filter list text into rules. // Tolerates both LF and CRLF line endings; skips empty lines. static void ParseFilterListRules(const nsTArray& aData, nsTArray& aRules) { nsDependentCSubstring content(reinterpret_cast(aData.Elements()), aData.Length()); for (const auto& line : content.Split('\n')) { nsCString rule(line); // Trim trailing CR for CRLF line endings. if (!rule.IsEmpty() && rule.Last() == '\r') { rule.Truncate(rule.Length() - 1); } if (!rule.IsEmpty()) { aRules.AppendElement(std::move(rule)); } } } void ContentClassifierService::RebuildEnginesFromStoredData() { mLock.AssertCurrentThreadOwns(); nsAutoCString blockListPref; Preferences::GetCString( "privacy.trackingprotection.content.protection.list_names", blockListPref); nsAutoCString annotateListPref; Preferences::GetCString( "privacy.trackingprotection.content.annotation.list_names", annotateListPref); MOZ_LOG_FMT(gContentClassifierLog, LogLevel::Debug, "RebuildEnginesFromStoredData - block lists: \"{}\", " "annotate lists: \"{}\", stored lists: {}", blockListPref, annotateListPref, mFilterListData.Count()); auto buildEngines = [this](const nsACString& aListNamesPref, nsTArray>& aEngines) MOZ_REQUIRES(mLock) { aEngines.Clear(); for (const auto& name : aListNamesPref.Split(',')) { nsAutoCString trimmedName(name); trimmedName.Trim(" "); if (trimmedName.IsEmpty()) { continue; } auto entry = mFilterListData.Lookup(trimmedName); if (!entry) { MOZ_LOG_FMT( gContentClassifierLog, LogLevel::Warning, "RebuildEnginesFromStoredData - list \"{}\" not found " "in stored data", trimmedName); continue; } nsTArray rules; ParseFilterListRules(entry.Data(), rules); auto engine = MakeUnique(); nsresult rv = engine->InitFromRules(rules); if (NS_FAILED(rv)) { MOZ_LOG_FMT( gContentClassifierLog, LogLevel::Error, "RebuildEnginesFromStoredData - failed to init engine " "for \"{}\": {:#x}", trimmedName, static_cast(rv)); continue; } MOZ_LOG_FMT(gContentClassifierLog, LogLevel::Info, "RebuildEnginesFromStoredData - loaded engine " "for \"{}\" with {} rules", trimmedName, rules.Length()); aEngines.AppendElement(std::move(engine)); } }; buildEngines(blockListPref, mBlockEngines); buildEngines(annotateListPref, mAnnotateEngines); } // HTTP-based list loading (test only) class FilterListLoader final : public nsIStreamLoaderObserver { public: NS_DECL_THREADSAFE_ISUPPORTS explicit FilterListLoader(nsTArray* aRules) : mRules(aRules) {} NS_IMETHOD OnStreamComplete(nsIStreamLoader* aLoader, nsISupports* aCtxt, nsresult aStatus, uint32_t aResultLength, const uint8_t* aResult) override { MOZ_ASSERT(NS_IsMainThread()); NS_ENSURE_SUCCESS(aStatus, aStatus); if (NS_FAILED(aStatus)) { MOZ_LOG(gContentClassifierLog, LogLevel::Debug, ("FilterListLoader::OnStreamComplete - failed with status 0x%x", static_cast(aStatus))); mPromiseHolder.RejectIfExists(aStatus, __func__); return aStatus; } nsAutoCString content(reinterpret_cast(aResult), aResultLength); for (const auto& line : content.Split('\n')) { if (!line.IsEmpty()) { mRules->AppendElement(line); } } MOZ_LOG(gContentClassifierLog, LogLevel::Debug, ("FilterListLoader::OnStreamComplete - loaded %zu rules", mRules->Length())); mPromiseHolder.ResolveIfExists(true, __func__); return NS_OK; } RefPtr Load(const nsACString& aURL) { MOZ_ASSERT(NS_IsMainThread()); nsCOMPtr uri; nsresult rv = NS_NewURI(getter_AddRefs(uri), aURL); NS_ENSURE_SUCCESS(rv, GenericPromise::CreateAndReject(rv, __func__)); nsCOMPtr channel; uint32_t loadFlags = nsIChannel::LOAD_BYPASS_URL_CLASSIFIER; rv = NS_NewChannel(getter_AddRefs(channel), uri, nsContentUtils::GetSystemPrincipal(), nsILoadInfo::SEC_ALLOW_CROSS_ORIGIN_SEC_CONTEXT_IS_NULL, nsIContentPolicy::TYPE_OTHER, nullptr, // nsICookieJarSettings nullptr, // aPerformanceStorage nullptr, // aLoadGroup nullptr, // aInterfaceRequestor loadFlags); NS_ENSURE_SUCCESS(rv, GenericPromise::CreateAndReject(rv, __func__)); nsCOMPtr loader; rv = NS_NewStreamLoader(getter_AddRefs(loader), this); NS_ENSURE_SUCCESS(rv, GenericPromise::CreateAndReject(rv, __func__)); rv = channel->AsyncOpen(loader); NS_ENSURE_SUCCESS(rv, GenericPromise::CreateAndReject(rv, __func__)); return mPromiseHolder.Ensure(__func__); } private: ~FilterListLoader() { mPromiseHolder.RejectIfExists(NS_ERROR_ABORT, __func__); } nsTArray* mRules; MozPromiseHolder mPromiseHolder; }; NS_IMPL_ISUPPORTS(FilterListLoader, nsIStreamLoaderObserver) void ContentClassifierService::LoadFilterLists() { MOZ_LOG(gContentClassifierLog, LogLevel::Debug, ("ContentClassifierService::LoadFilterLists - loading filter lists")); nsTArray> promises; nsAutoCString blockListPref; Preferences::GetCString( "privacy.trackingprotection.content.protection.test_list_urls", blockListPref); nsTArray blockListURLs; for (const nsACString& url : blockListPref.Split('|')) { if (!url.IsEmpty()) { blockListURLs.AppendElement(url); MOZ_LOG( gContentClassifierLog, LogLevel::Debug, ("LoadFilterLists - block list URL: %s", nsAutoCString(url).get())); } } nsAutoCString annotationListPref; Preferences::GetCString( "privacy.trackingprotection.content.annotation.test_list_urls", annotationListPref); nsTArray annotationListURLs; for (const nsACString& url : annotationListPref.Split('|')) { if (!url.IsEmpty()) { annotationListURLs.AppendElement(url); MOZ_LOG(gContentClassifierLog, LogLevel::Debug, ("LoadFilterLists - annotation list URL: %s", nsAutoCString(url).get())); } } nsTArray> blockFilterRules; nsTArray> annotateFilterRules; blockFilterRules.SetLength(blockListURLs.Length()); annotateFilterRules.SetLength(annotationListURLs.Length()); for (size_t i = 0; i < blockListURLs.Length(); ++i) { RefPtr loader = new FilterListLoader(&blockFilterRules[i]); promises.AppendElement(loader->Load(blockListURLs[i])); } for (size_t i = 0; i < annotationListURLs.Length(); ++i) { RefPtr loader = new FilterListLoader(&annotateFilterRules[i]); promises.AppendElement(loader->Load(annotationListURLs[i])); } GenericPromise::AllSettled(GetMainThreadSerialEventTarget(), promises) ->Then( GetMainThreadSerialEventTarget(), __func__, [self = RefPtr{this}, annotateFilterRules = std::move(annotateFilterRules), blockFilterRules = std::move(blockFilterRules)]( const GenericPromise::AllSettledPromiseType::ResolveOrRejectValue& aResults) { ReleasableMutexAutoLock lock(self->mLock); self->mBlockEngines.Clear(); self->mAnnotateEngines.Clear(); for (const auto& rules : blockFilterRules) { auto engine = MakeUnique(); nsresult rv = engine->InitFromRules(rules); if (NS_FAILED(rv)) { continue; } self->mBlockEngines.AppendElement(std::move(engine)); } for (const auto& rules : annotateFilterRules) { auto engine = MakeUnique(); nsresult rv = engine->InitFromRules(rules); if (NS_FAILED(rv)) { continue; } self->mAnnotateEngines.AppendElement(std::move(engine)); } lock.Unlock(); NotifyListsLoadedForTesting(); }); } } // namespace mozilla