Index: chrome/browser/safe_browsing/incident_reporting_service.cc |
diff --git a/chrome/browser/safe_browsing/incident_reporting_service.cc b/chrome/browser/safe_browsing/incident_reporting_service.cc |
index 6c8ce1b9f562fb94fc295ca70e1b3993c8c8d856..51df03baefa9d89c06865476b223b2f8c789fb0f 100644 |
--- a/chrome/browser/safe_browsing/incident_reporting_service.cc |
+++ b/chrome/browser/safe_browsing/incident_reporting_service.cc |
@@ -13,8 +13,10 @@ |
#include "base/prefs/pref_service.h" |
#include "base/prefs/scoped_user_pref_update.h" |
#include "base/process/process_info.h" |
+#include "base/single_thread_task_runner.h" |
#include "base/stl_util.h" |
#include "base/strings/string_number_conversions.h" |
+#include "base/thread_task_runner_handle.h" |
#include "base/threading/sequenced_worker_pool.h" |
#include "base/values.h" |
#include "chrome/browser/browser_process.h" |
@@ -70,6 +72,9 @@ struct PersistentIncidentState { |
// The amount of time the service will wait to collate incidents. |
const int64 kDefaultUploadDelayMs = 1000 * 60; // one minute |
+// The amount of time between running delayed analysis callbacks. |
+const int64 kDefaultCallbackIntervalMs = 1000 * 20; |
+ |
// Returns the number of incidents contained in |incident|. The result is |
// expected to be 1. Used in DCHECKs. |
size_t CountIncidents(const ClientIncidentReport_IncidentData& incident) { |
@@ -157,6 +162,19 @@ void MarkIncidentsAsReported(const std::vector<PersistentIncidentState>& states, |
} |
} |
+// Runs |callback| on the thread to which |thread_runner| belongs. The callback |
+// is run immediately if this function is called on |thread_runner|'s thread. |
+void AddIncidentOnOriginThread( |
+ const AddIncidentCallback& callback, |
+ scoped_refptr<base::SingleThreadTaskRunner> thread_runner, |
+ scoped_ptr<ClientIncidentReport_IncidentData> incident) { |
+ if (thread_runner->BelongsToCurrentThread()) |
+ callback.Run(incident.Pass()); |
+ else |
+ thread_runner->PostTask(FROM_HERE, |
+ base::Bind(callback, base::Passed(&incident))); |
+} |
+ |
} // namespace |
struct IncidentReportingService::ProfileContext { |
@@ -220,11 +238,16 @@ IncidentReportingService::IncidentReportingService( |
->GetTaskRunnerWithShutdownBehavior( |
base::SequencedWorkerPool::SKIP_ON_SHUTDOWN)), |
environment_collection_pending_(), |
- collection_timeout_pending_(), |
- upload_timer_(FROM_HERE, |
- base::TimeDelta::FromMilliseconds(kDefaultUploadDelayMs), |
- this, |
- &IncidentReportingService::OnCollectionTimeout), |
+ collation_timeout_pending_(), |
+ collation_timer_(FROM_HERE, |
+ base::TimeDelta::FromMilliseconds(kDefaultUploadDelayMs), |
+ this, |
+ &IncidentReportingService::OnCollationTimeout), |
+ delayed_analysis_callbacks_( |
+ base::TimeDelta::FromMilliseconds(kDefaultCallbackIntervalMs), |
+ content::BrowserThread::GetBlockingPool() |
+ ->GetTaskRunnerWithShutdownBehavior( |
+ base::SequencedWorkerPool::SKIP_ON_SHUTDOWN)), |
receiver_weak_ptr_factory_(this), |
weak_ptr_factory_(this) { |
notification_registrar_.Add(this, |
@@ -270,6 +293,56 @@ IncidentReportingService::CreatePreferenceValidationDelegate(Profile* profile) { |
new PreferenceValidationDelegate(GetAddIncidentCallback(profile))); |
} |
+void IncidentReportingService::RegisterDelayedAnalysisCallback( |
+ const DelayedAnalysisCallback& callback) { |
+ DCHECK(thread_checker_.CalledOnValidThread()); |
+ |
+ // |callback| will be run on the blocking pool, so it will likely run the |
+ // AddIncidentCallback there as well. Bounce the run of that callback back to |
+ // the current thread via AddIncidentOnOriginThread. |
+ delayed_analysis_callbacks_.RegisterCallback( |
+ base::Bind(callback, |
+ base::Bind(&AddIncidentOnOriginThread, |
+ GetAddIncidentCallback(NULL), |
+ base::ThreadTaskRunnerHandle::Get()))); |
+ |
+ // Start running the callbacks if any profiles are participating in safe |
+ // browsing. If none are now, running will commence if/when a participaing |
+ // profile is added. |
+ if (FindEligibleProfile()) |
+ delayed_analysis_callbacks_.Start(); |
+} |
+ |
+IncidentReportingService::IncidentReportingService( |
+ SafeBrowsingService* safe_browsing_service, |
+ const scoped_refptr<net::URLRequestContextGetter>& request_context_getter, |
+ base::TimeDelta delayed_task_interval, |
+ const scoped_refptr<base::TaskRunner>& delayed_task_runner) |
+ : database_manager_(safe_browsing_service ? |
+ safe_browsing_service->database_manager() : NULL), |
+ url_request_context_getter_(request_context_getter), |
+ collect_environment_data_fn_(&CollectEnvironmentData), |
+ environment_collection_task_runner_( |
+ content::BrowserThread::GetBlockingPool() |
+ ->GetTaskRunnerWithShutdownBehavior( |
+ base::SequencedWorkerPool::SKIP_ON_SHUTDOWN)), |
+ environment_collection_pending_(), |
+ collation_timeout_pending_(), |
+ collation_timer_(FROM_HERE, |
+ base::TimeDelta::FromMilliseconds(kDefaultUploadDelayMs), |
+ this, |
+ &IncidentReportingService::OnCollationTimeout), |
+ delayed_analysis_callbacks_(delayed_task_interval, delayed_task_runner), |
+ receiver_weak_ptr_factory_(this), |
+ weak_ptr_factory_(this) { |
+ notification_registrar_.Add(this, |
+ chrome::NOTIFICATION_PROFILE_ADDED, |
+ content::NotificationService::AllSources()); |
+ notification_registrar_.Add(this, |
+ chrome::NOTIFICATION_PROFILE_DESTROYED, |
+ content::NotificationService::AllSources()); |
+} |
+ |
void IncidentReportingService::SetCollectEnvironmentHook( |
CollectEnvironmentDataFn collect_environment_data_hook, |
const scoped_refptr<base::TaskRunner>& task_runner) { |
@@ -294,17 +367,33 @@ void IncidentReportingService::OnProfileAdded(Profile* profile) { |
ProfileContext* context = GetOrCreateProfileContext(profile); |
context->added = true; |
+ const bool safe_browsing_enabled = |
+ profile->GetPrefs()->GetBoolean(prefs::kSafeBrowsingEnabled); |
+ |
+ // Start processing delayed analysis callbacks if this new profile |
+ // participates in safe browsing. Start is idempotent, so this is safe even if |
+ // they're already running. |
+ if (safe_browsing_enabled) |
+ delayed_analysis_callbacks_.Start(); |
+ |
+ // Start a new report if this profile participates in safe browsing and there |
+ // are process-wide incidents. |
+ if (safe_browsing_enabled && GetProfileContext(NULL)) |
+ BeginReportProcessing(); |
+ |
+ // TODO(grt): register for pref change notifications to start delayed analysis |
+ // and/or report processing if sb is currently disabled but subsequently |
+ // enabled. |
+ |
// Nothing else to do if a report is not being assembled. |
if (!report_) |
return; |
- // Drop all incidents received prior to creation if the profile is not |
- // participating in safe browsing. |
- if (!context->incidents.empty() && |
- !profile->GetPrefs()->GetBoolean(prefs::kSafeBrowsingEnabled)) { |
- for (size_t i = 0; i < context->incidents.size(); ++i) { |
+ // Drop all incidents associated with this profile that were received prior to |
+ // its addition if the profile is not participating in safe browsing. |
+ if (!context->incidents.empty() && !safe_browsing_enabled) { |
+ for (size_t i = 0; i < context->incidents.size(); ++i) |
LogIncidentDataType(DROPPED, *context->incidents[i]); |
- } |
context->incidents.clear(); |
} |
@@ -362,35 +451,51 @@ void IncidentReportingService::OnProfileDestroyed(Profile* profile) { |
uploads_[i]->profiles_to_state.erase(profile); |
} |
+Profile* IncidentReportingService::FindEligibleProfile() const { |
+ Profile* candidate = NULL; |
+ for (ProfileContextCollection::const_iterator scan = profiles_.begin(); |
+ scan != profiles_.end(); |
+ ++scan) { |
+ // Skip over profiles that have yet to be added to the profile manager. |
+ // This will also skip over the NULL-profile context used to hold |
+ // process-wide incidents. |
+ if (!scan->second->added) |
+ continue; |
+ PrefService* prefs = scan->first->GetPrefs(); |
+ if (prefs->GetBoolean(prefs::kSafeBrowsingEnabled)) { |
+ if (!candidate) |
+ candidate = scan->first; |
+ if (prefs->GetBoolean(prefs::kSafeBrowsingExtendedReportingEnabled)) { |
+ candidate = scan->first; |
+ break; |
+ } |
+ } |
+ } |
+ return candidate; |
+} |
+ |
void IncidentReportingService::AddIncident( |
Profile* profile, |
scoped_ptr<ClientIncidentReport_IncidentData> incident_data) { |
DCHECK(thread_checker_.CalledOnValidThread()); |
- // Incidents outside the context of a profile are not supported at the moment. |
- DCHECK(profile); |
DCHECK_EQ(1U, CountIncidents(*incident_data)); |
ProfileContext* context = GetProfileContext(profile); |
// It is forbidden to call this function with a destroyed profile. |
DCHECK(context); |
+ // If this is a process-wide incident, the context must not indicate that the |
+ // profile (which is NULL) has been added to the profile manager. |
+ DCHECK(profile || !context->added); |
- // Drop the incident immediately if profile creation has completed and the |
- // profile is not participating in safe browsing. Preference evaluation is |
- // deferred until OnProfileAdded() if profile creation has not yet |
- // completed. |
+ // Drop the incident immediately if the profile has already been added to the |
+ // manager and is not participating in safe browsing. Preference evaluation is |
+ // deferred until OnProfileAdded() otherwise. |
if (context->added && |
!profile->GetPrefs()->GetBoolean(prefs::kSafeBrowsingEnabled)) { |
LogIncidentDataType(DROPPED, *incident_data); |
return; |
} |
- // Start assembling a new report if this is the first incident ever or the |
- // first since the last upload. |
- if (!report_) { |
- report_.reset(new ClientIncidentReport()); |
- first_incident_time_ = base::Time::Now(); |
- } |
- |
// Provide time to the new incident if the caller didn't do so. |
if (!incident_data->has_incident_time_msec()) |
incident_data->set_incident_time_msec(base::Time::Now().ToJavaTime()); |
@@ -398,6 +503,10 @@ void IncidentReportingService::AddIncident( |
// Take ownership of the incident. |
context->incidents.push_back(incident_data.release()); |
+ // Remember when the first incident for this report arrived. |
+ if (first_incident_time_.is_null()) |
+ first_incident_time_ = base::Time::Now(); |
+ // Log the time between the previous incident and this one. |
if (!last_incident_time_.is_null()) { |
UMA_HISTOGRAM_TIMES("SBIRS.InterIncidentTime", |
base::TimeTicks::Now() - last_incident_time_); |
@@ -406,14 +515,30 @@ void IncidentReportingService::AddIncident( |
// Persist the incident data. |
- // Restart the delay timer to send the report upon expiration. |
- collection_timeout_pending_ = true; |
- upload_timer_.Reset(); |
+ // Start assembling a new report if this is the first incident ever or the |
+ // first since the last upload. |
+ BeginReportProcessing(); |
+} |
+ |
+void IncidentReportingService::BeginReportProcessing() { |
+ DCHECK(thread_checker_.CalledOnValidThread()); |
+ |
+ // Creates a new report if needed. |
+ if (!report_) |
+ report_.reset(new ClientIncidentReport()); |
+ // Ensure that collection tasks are running (calls are idempotent). |
+ BeginIncidentCollation(); |
BeginEnvironmentCollection(); |
BeginDownloadCollection(); |
} |
+void IncidentReportingService::BeginIncidentCollation() { |
+ // Restart the delay timer to send the report upon expiration. |
+ collation_timeout_pending_ = true; |
+ collation_timer_.Reset(); |
+} |
+ |
void IncidentReportingService::BeginEnvironmentCollection() { |
DCHECK(thread_checker_.CalledOnValidThread()); |
DCHECK(report_); |
@@ -462,7 +587,6 @@ void IncidentReportingService::OnEnvironmentDataCollected( |
first_incident_time_ - base::CurrentProcessInfo::CreationTime(); |
environment_data->mutable_process()->set_uptime_msec(uptime.InMilliseconds()); |
#endif |
- first_incident_time_ = base::Time(); |
report_->set_allocated_environment(environment_data.release()); |
@@ -474,34 +598,35 @@ void IncidentReportingService::OnEnvironmentDataCollected( |
} |
bool IncidentReportingService::WaitingToCollateIncidents() { |
- return collection_timeout_pending_; |
+ return collation_timeout_pending_; |
} |
void IncidentReportingService::CancelIncidentCollection() { |
- collection_timeout_pending_ = false; |
+ collation_timeout_pending_ = false; |
last_incident_time_ = base::TimeTicks(); |
report_.reset(); |
} |
-void IncidentReportingService::OnCollectionTimeout() { |
+void IncidentReportingService::OnCollationTimeout() { |
DCHECK(thread_checker_.CalledOnValidThread()); |
// Exit early if collection was cancelled. |
- if (!collection_timeout_pending_) |
+ if (!collation_timeout_pending_) |
return; |
- // Wait another round if incidents have come in from a profile that has yet to |
- // complete creation. |
+ // Wait another round if profile-bound incidents have come in from a profile |
+ // that has yet to complete creation. |
for (ProfileContextCollection::iterator scan = profiles_.begin(); |
scan != profiles_.end(); |
++scan) { |
- if (!scan->second->added && !scan->second->incidents.empty()) { |
- upload_timer_.Reset(); |
+ if (scan->first && !scan->second->added && |
+ !scan->second->incidents.empty()) { |
+ collation_timer_.Reset(); |
return; |
} |
} |
- collection_timeout_pending_ = false; |
+ collation_timeout_pending_ = false; |
UploadIfCollectionComplete(); |
} |
@@ -581,6 +706,7 @@ void IncidentReportingService::UploadIfCollectionComplete() { |
// Take ownership of the report and clear things for future reports. |
scoped_ptr<ClientIncidentReport> report(report_.Pass()); |
+ first_incident_time_ = base::Time(); |
last_incident_time_ = base::TimeTicks(); |
// Drop the report if no executable download was found. |
@@ -601,8 +727,25 @@ void IncidentReportingService::UploadIfCollectionComplete() { |
prefs::kMetricsReportingEnabled)); |
} |
- // Check for extended consent in any profile while collecting incidents. |
- process->set_extended_consent(false); |
+ // Find the profile that benefits from the strongest protections. |
+ Profile* eligible_profile = FindEligibleProfile(); |
+ process->set_extended_consent( |
+ eligible_profile ? eligible_profile->GetPrefs()->GetBoolean( |
+ prefs::kSafeBrowsingExtendedReportingEnabled) : |
+ false); |
+ |
+ // Associate process-wide incidents with the profile that benefits from the |
+ // strongest safe browsing protections. |
+ ProfileContext* null_context = GetProfileContext(NULL); |
+ if (null_context && !null_context->incidents.empty() && eligible_profile) { |
+ ProfileContext* eligible_context = GetProfileContext(eligible_profile); |
+ // Move the incidents to the target context. |
+ eligible_context->incidents.insert(eligible_context->incidents.end(), |
+ null_context->incidents.begin(), |
+ null_context->incidents.end()); |
+ null_context->incidents.weak_clear(); |
+ } |
+ |
// Collect incidents across all profiles participating in safe browsing. Drop |
// incidents if the profile stopped participating before collection completed. |
// Prune previously submitted incidents. |
@@ -612,12 +755,11 @@ void IncidentReportingService::UploadIfCollectionComplete() { |
for (ProfileContextCollection::iterator scan = profiles_.begin(); |
scan != profiles_.end(); |
++scan) { |
+ // Bypass process-wide incidents that have not yet been associated with a |
+ // profile. |
+ if (!scan->first) |
+ continue; |
PrefService* prefs = scan->first->GetPrefs(); |
- if (process && |
- prefs->GetBoolean(prefs::kSafeBrowsingExtendedReportingEnabled)) { |
- process->set_extended_consent(true); |
- process = NULL; // Don't check any more once one is found. |
- } |
ProfileContext* context = scan->second; |
if (context->incidents.empty()) |
continue; |