summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorgrt@chromium.org <grt@chromium.org@0039d316-1c4b-4281-b951-d872f2087c98>2014-08-07 11:05:31 +0000
committergrt@chromium.org <grt@chromium.org@0039d316-1c4b-4281-b951-d872f2087c98>2014-08-07 11:05:31 +0000
commitf3fcec39cf71c69e80c23f088b207ca4a419df05 (patch)
tree1be935a34430d05d96f0c35375228feea628237e
parent891f17cd94abd488fe06f9dd342acbfb8859f95d (diff)
downloadchromium_src-f3fcec39cf71c69e80c23f088b207ca4a419df05.zip
chromium_src-f3fcec39cf71c69e80c23f088b207ca4a419df05.tar.gz
chromium_src-f3fcec39cf71c69e80c23f088b207ca4a419df05.tar.bz2
Support for process-wide incidents in the safe browsing incident reporting service.
BUG=399428 R=robertshield@chromium.org Review URL: https://codereview.chromium.org/441453002 git-svn-id: svn://svn.chromium.org/chrome/trunk/src@288028 0039d316-1c4b-4281-b951-d872f2087c98
-rw-r--r--chrome/browser/safe_browsing/delayed_analysis_callback.h22
-rw-r--r--chrome/browser/safe_browsing/delayed_callback_runner.cc51
-rw-r--r--chrome/browser/safe_browsing/delayed_callback_runner.h66
-rw-r--r--chrome/browser/safe_browsing/delayed_callback_runner_unittest.cc160
-rw-r--r--chrome/browser/safe_browsing/incident_reporting_service.cc230
-rw-r--r--chrome/browser/safe_browsing/incident_reporting_service.h56
-rw-r--r--chrome/browser/safe_browsing/incident_reporting_service_unittest.cc215
-rw-r--r--chrome/browser/safe_browsing/safe_browsing_service.cc8
-rw-r--r--chrome/browser/safe_browsing/safe_browsing_service.h7
-rw-r--r--chrome/chrome_browser.gypi3
-rw-r--r--chrome/chrome_tests_unit.gypi1
11 files changed, 761 insertions, 58 deletions
diff --git a/chrome/browser/safe_browsing/delayed_analysis_callback.h b/chrome/browser/safe_browsing/delayed_analysis_callback.h
new file mode 100644
index 0000000..e36282f
--- /dev/null
+++ b/chrome/browser/safe_browsing/delayed_analysis_callback.h
@@ -0,0 +1,22 @@
+// Copyright 2014 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef CHROME_BROWSER_SAFE_BROWSING_DELAYED_ANALYSIS_CALLBACK_H_
+#define CHROME_BROWSER_SAFE_BROWSING_DELAYED_ANALYSIS_CALLBACK_H_
+
+#include "base/callback_forward.h"
+#include "chrome/browser/safe_browsing/add_incident_callback.h"
+
+namespace safe_browsing {
+
+// A callback used by external components to register a process-wide analysis
+// step. The callback will be run after some delay following process launch in
+// the blocking pool. The argument is a callback by which the consumer can add
+// incidents to the incident reporting service.
+typedef base::Callback<void(const AddIncidentCallback&)>
+ DelayedAnalysisCallback;
+
+} // namespace safe_browsing
+
+#endif // CHROME_BROWSER_SAFE_BROWSING_DELAYED_ANALYSIS_CALLBACK_H_
diff --git a/chrome/browser/safe_browsing/delayed_callback_runner.cc b/chrome/browser/safe_browsing/delayed_callback_runner.cc
new file mode 100644
index 0000000..f344459
--- /dev/null
+++ b/chrome/browser/safe_browsing/delayed_callback_runner.cc
@@ -0,0 +1,51 @@
+// Copyright 2014 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "chrome/browser/safe_browsing/delayed_callback_runner.h"
+
+#include "base/location.h"
+
+namespace safe_browsing {
+
+DelayedCallbackRunner::DelayedCallbackRunner(
+ base::TimeDelta delay,
+ const scoped_refptr<base::TaskRunner>& task_runner)
+ : task_runner_(task_runner),
+ next_callback_(callbacks_.end()),
+ timer_(FROM_HERE, delay, this, &DelayedCallbackRunner::OnTimer) {
+}
+
+DelayedCallbackRunner::~DelayedCallbackRunner() {
+}
+
+void DelayedCallbackRunner::RegisterCallback(const base::Closure& callback) {
+ DCHECK(thread_checker_.CalledOnValidThread());
+ callbacks_.push_back(callback);
+}
+
+void DelayedCallbackRunner::Start() {
+ DCHECK(thread_checker_.CalledOnValidThread());
+
+ // Nothing to do if the runner is already running or nothing has been added.
+ if (next_callback_ != callbacks_.end() || callbacks_.empty())
+ return;
+
+ // Prime the system with the first callback.
+ next_callback_ = callbacks_.begin();
+
+ // Point the starter pistol in the air and pull the trigger.
+ timer_.Reset();
+}
+
+void DelayedCallbackRunner::OnTimer() {
+ // Run the next callback on the task runner.
+ task_runner_->PostTask(FROM_HERE, *next_callback_);
+
+ // Remove this callback and get ready for the next if there is one.
+ next_callback_ = callbacks_.erase(next_callback_);
+ if (next_callback_ != callbacks_.end())
+ timer_.Reset();
+}
+
+} // namespace safe_browsing
diff --git a/chrome/browser/safe_browsing/delayed_callback_runner.h b/chrome/browser/safe_browsing/delayed_callback_runner.h
new file mode 100644
index 0000000..ff37837
--- /dev/null
+++ b/chrome/browser/safe_browsing/delayed_callback_runner.h
@@ -0,0 +1,66 @@
+// Copyright 2014 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef CHROME_BROWSER_SAFE_BROWSING_DELAYED_CALLBACK_RUNNER_H_
+#define CHROME_BROWSER_SAFE_BROWSING_DELAYED_CALLBACK_RUNNER_H_
+
+#include <list>
+
+#include "base/callback_forward.h"
+#include "base/macros.h"
+#include "base/memory/ref_counted.h"
+#include "base/task_runner.h"
+#include "base/threading/thread_checker.h"
+#include "base/time/time.h"
+#include "base/timer/timer.h"
+
+namespace safe_browsing {
+
+// Runs callbacks on a given task runner, waiting a certain amount of time
+// between each. The delay also applies to running the first callback (i.e.,
+// the first callback will be run some time after Start() is invoked). Callbacks
+// are deleted after they are run. Start() is idempotent: calling it while the
+// runner is doing its job has no effect.
+class DelayedCallbackRunner {
+ public:
+ // Constructs an instance that runs tasks on |callback_runner|, waiting for
+ // |delay| time to pass before and between each callback.
+ DelayedCallbackRunner(base::TimeDelta delay,
+ const scoped_refptr<base::TaskRunner>& task_runner);
+ ~DelayedCallbackRunner();
+
+ // Registers |callback| with the runner. A copy of |callback| is held until it
+ // is run.
+ void RegisterCallback(const base::Closure& callback);
+
+ // Starts running the callbacks after the delay.
+ void Start();
+
+ private:
+ typedef std::list<base::Closure> CallbackList;
+
+ // A callback invoked by the timer to run the next callback. The timer is
+ // restarted to process the next callback if there is one.
+ void OnTimer();
+
+ base::ThreadChecker thread_checker_;
+
+ // The runner on which callbacks are to be run.
+ scoped_refptr<base::TaskRunner> task_runner_;
+
+ // The list of callbacks to run. Callbacks are removed when run.
+ CallbackList callbacks_;
+
+ // callbacks_.end() when no work is being done. Any other value otherwise.
+ CallbackList::iterator next_callback_;
+
+ // A timer upon the firing of which the next callback will be run.
+ base::DelayTimer<DelayedCallbackRunner> timer_;
+
+ DISALLOW_COPY_AND_ASSIGN(DelayedCallbackRunner);
+};
+
+} // namespace safe_browsing
+
+#endif // CHROME_BROWSER_SAFE_BROWSING_DELAYED_CALLBACK_RUNNER_H_
diff --git a/chrome/browser/safe_browsing/delayed_callback_runner_unittest.cc b/chrome/browser/safe_browsing/delayed_callback_runner_unittest.cc
new file mode 100644
index 0000000..c1b4a79
--- /dev/null
+++ b/chrome/browser/safe_browsing/delayed_callback_runner_unittest.cc
@@ -0,0 +1,160 @@
+// Copyright 2014 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "chrome/browser/safe_browsing/delayed_callback_runner.h"
+
+#include <map>
+#include <string>
+
+#include "base/callback.h"
+#include "base/macros.h"
+#include "base/test/test_simple_task_runner.h"
+#include "base/thread_task_runner_handle.h"
+#include "testing/gtest/include/gtest/gtest.h"
+
+namespace {
+
+// A class of objects that invoke a callback upon destruction. This is used as
+// an owned argument on callbacks given to a DelayedCallbackRunner under test.
+class CallbackArgument {
+ public:
+ explicit CallbackArgument(const base::Closure& on_delete)
+ : on_delete_(on_delete) {}
+ ~CallbackArgument() { on_delete_.Run(); }
+
+ private:
+ base::Closure on_delete_;
+
+ DISALLOW_COPY_AND_ASSIGN(CallbackArgument);
+};
+
+} // namespace
+
+// A test fixture that prepares a DelayedCallbackRunner instance for use and
+// tracks the lifecycle of callbacks sent to it.
+class DelayedCallbackRunnerTest : public testing::Test {
+ public:
+ // Registers a callback that will record its running and destruction to the
+ // test fixture under the given name.
+ void RegisterTestCallback(const std::string& name) {
+ callbacks_[name] = CallbackState();
+ instance_->RegisterCallback(MakeCallback(name));
+ }
+
+ protected:
+ DelayedCallbackRunnerTest()
+ : task_runner_(new base::TestSimpleTaskRunner),
+ thread_task_runner_handle_(task_runner_) {}
+
+ virtual void SetUp() OVERRIDE {
+ instance_.reset(new safe_browsing::DelayedCallbackRunner(
+ base::TimeDelta::FromMilliseconds(1), // ignored by simple runner.
+ task_runner_));
+ }
+
+ virtual void TearDown() OVERRIDE { instance_.reset(); }
+
+ void OnRun(const std::string& name, CallbackArgument* arg) {
+ EXPECT_FALSE(callbacks_[name].run);
+ callbacks_[name].run = true;
+ }
+
+ void OnDelete(const std::string& name) {
+ EXPECT_FALSE(callbacks_[name].deleted);
+ callbacks_[name].deleted = true;
+ }
+
+ // Returns a callback argument that calls the test fixture's OnDelete method
+ // on behalf of the given callback name.
+ scoped_ptr<CallbackArgument> MakeCallbackArgument(const std::string& name) {
+ return make_scoped_ptr(new CallbackArgument(base::Bind(
+ &DelayedCallbackRunnerTest::OnDelete, base::Unretained(this), name)));
+ }
+
+ // Returns a closure that calls |OnRun| when run and |OnDelete| when deleted
+ // on behalf of the given callback name.
+ base::Closure MakeCallback(const std::string& name) {
+ return base::Bind(&DelayedCallbackRunnerTest::OnRun,
+ base::Unretained(this),
+ name,
+ base::Owned(MakeCallbackArgument(name).release()));
+ }
+
+ bool CallbackWasRun(const std::string& name) { return callbacks_[name].run; }
+
+ bool CallbackWasDeleted(const std::string& name) {
+ return callbacks_[name].deleted;
+ }
+
+ scoped_refptr<base::TestSimpleTaskRunner> task_runner_;
+ base::ThreadTaskRunnerHandle thread_task_runner_handle_;
+ scoped_ptr<safe_browsing::DelayedCallbackRunner> instance_;
+
+ private:
+ struct CallbackState {
+ CallbackState() : run(), deleted() {}
+ bool run;
+ bool deleted;
+ };
+
+ std::map<std::string, CallbackState> callbacks_;
+};
+
+// Tests that a callback is deleted when not run before the runner is destroyed.
+TEST_F(DelayedCallbackRunnerTest, NotRunDeleted) {
+ const std::string name("one");
+ RegisterTestCallback(name);
+ instance_.reset();
+ EXPECT_FALSE(CallbackWasRun(name));
+ EXPECT_TRUE(CallbackWasDeleted(name));
+}
+
+// Tests that a callback is run and deleted while the runner is alive.
+TEST_F(DelayedCallbackRunnerTest, RunDeleted) {
+ const std::string name("one");
+ RegisterTestCallback(name);
+ instance_->Start();
+ task_runner_->RunUntilIdle();
+ EXPECT_TRUE(CallbackWasRun(name));
+ EXPECT_TRUE(CallbackWasDeleted(name));
+}
+
+// Tests that a callback registered after Start() is called is also run and
+// deleted.
+TEST_F(DelayedCallbackRunnerTest, AddWhileRunningRun) {
+ const std::string name("one");
+ const std::string name2("two");
+
+ // Post a task to register a new callback after Start() is called.
+ task_runner_->PostTask(
+ FROM_HERE,
+ base::Bind(&DelayedCallbackRunnerTest::RegisterTestCallback,
+ base::Unretained(this),
+ name2));
+
+ RegisterTestCallback(name);
+ instance_->Start();
+ task_runner_->RunUntilIdle();
+ EXPECT_TRUE(CallbackWasRun(name));
+ EXPECT_TRUE(CallbackWasDeleted(name));
+ EXPECT_TRUE(CallbackWasRun(name2));
+ EXPECT_TRUE(CallbackWasDeleted(name2));
+}
+
+TEST_F(DelayedCallbackRunnerTest, MultipleRuns) {
+ const std::string name("one");
+ const std::string name2("two");
+
+ RegisterTestCallback(name);
+ instance_->Start();
+ task_runner_->RunUntilIdle();
+ EXPECT_TRUE(CallbackWasRun(name));
+ EXPECT_TRUE(CallbackWasDeleted(name));
+
+ RegisterTestCallback(name2);
+ instance_->Start();
+ task_runner_->RunUntilIdle();
+ EXPECT_TRUE(CallbackWasRun(name2));
+ EXPECT_TRUE(CallbackWasDeleted(name2));
+}
diff --git a/chrome/browser/safe_browsing/incident_reporting_service.cc b/chrome/browser/safe_browsing/incident_reporting_service.cc
index 6c8ce1b..51df03b 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;
diff --git a/chrome/browser/safe_browsing/incident_reporting_service.h b/chrome/browser/safe_browsing/incident_reporting_service.h
index bd02472..196885f 100644
--- a/chrome/browser/safe_browsing/incident_reporting_service.h
+++ b/chrome/browser/safe_browsing/incident_reporting_service.h
@@ -19,6 +19,8 @@
#include "base/time/time.h"
#include "base/timer/timer.h"
#include "chrome/browser/safe_browsing/add_incident_callback.h"
+#include "chrome/browser/safe_browsing/delayed_analysis_callback.h"
+#include "chrome/browser/safe_browsing/delayed_callback_runner.h"
#include "chrome/browser/safe_browsing/incident_report_uploader.h"
#include "chrome/browser/safe_browsing/last_download_finder.h"
#include "content/public/browser/notification_observer.h"
@@ -54,11 +56,13 @@ class ClientIncidentReport_IncidentData;
// begins operation when an incident is reported via the AddIncident method.
// Incidents reported from a profile that is loading are held until the profile
// is fully created. Incidents originating from profiles that do not participate
-// in safe browsing are dropped. Following the addition of an incident that is
-// not dropped, the service collects environmental data, finds the most recent
-// binary download, and waits a bit. Additional incidents that arrive during
-// this time are collated with the initial incident. Finally, already-reported
-// incidents are pruned and any remaining are uploaded in an incident report.
+// in safe browsing are dropped. Process-wide incidents are affiliated with a
+// profile that participates in safe browsing when one becomes available.
+// Following the addition of an incident that is not dropped, the service
+// collects environmental data, finds the most recent binary download, and waits
+// a bit. Additional incidents that arrive during this time are collated with
+// the initial incident. Finally, already-reported incidents are pruned and any
+// remaining are uploaded in an incident report.
class IncidentReportingService : public content::NotificationObserver {
public:
IncidentReportingService(SafeBrowsingService* safe_browsing_service,
@@ -83,11 +87,22 @@ class IncidentReportingService : public content::NotificationObserver {
scoped_ptr<TrackedPreferenceValidationDelegate>
CreatePreferenceValidationDelegate(Profile* profile);
+ // Registers |callback| to be run after some delay following process launch.
+ void RegisterDelayedAnalysisCallback(const DelayedAnalysisCallback& callback);
+
protected:
// A pointer to a function that populates a protobuf with environment data.
typedef void (*CollectEnvironmentDataFn)(
ClientIncidentReport_EnvironmentData*);
+ // For testing so that the TaskRunner used for delayed analysis callbacks can
+ // be specified.
+ 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);
+
// Sets the function called by the service to collect environment data and the
// task runner on which it is called. Used by unit tests to provide a fake
// environment data collector.
@@ -131,11 +146,23 @@ class IncidentReportingService : public content::NotificationObserver {
// but not yet uploaded are dropped.
void OnProfileDestroyed(Profile* profile);
+ // Returns an initialized profile that participates in safe browsing. Profiles
+ // participating in extended safe browsing are preferred.
+ Profile* FindEligibleProfile() const;
+
// Adds |incident_data| to the service. The incident_time_msec field is
// populated with the current time if the caller has not already done so.
void AddIncident(Profile* profile,
scoped_ptr<ClientIncidentReport_IncidentData> incident_data);
+ // Begins processing a report. If processing is already underway, ensures that
+ // collection tasks have completed or are running.
+ void BeginReportProcessing();
+
+ // Begins the process of collating incidents by waiting for incidents to
+ // arrive. This function is idempotent.
+ void BeginIncidentCollation();
+
// Starts a task to collect environment data in the blocking pool.
void BeginEnvironmentCollection();
@@ -159,10 +186,11 @@ class IncidentReportingService : public content::NotificationObserver {
// Cancels the collection timeout.
void CancelIncidentCollection();
- // A callback invoked on the UI thread after which incident collection has
+ // A callback invoked on the UI thread after which incident collation has
// completed. Incident report processing continues, either by waiting for
- // environment data to arrive or by sending an incident report.
- void OnCollectionTimeout();
+ // environment data or the most recent download to arrive or by sending an
+ // incident report.
+ void OnCollationTimeout();
// Starts the asynchronous process of finding the most recent executable
// download if one is not currently being search for and/or has not already
@@ -234,12 +262,12 @@ class IncidentReportingService : public content::NotificationObserver {
bool environment_collection_pending_;
// True when an incident has been received and the service is waiting for the
- // upload_timer_ to fire.
- bool collection_timeout_pending_;
+ // collation_timer_ to fire.
+ bool collation_timeout_pending_;
// A timer upon the firing of which the service will report received
// incidents.
- base::DelayTimer<IncidentReportingService> upload_timer_;
+ base::DelayTimer<IncidentReportingService> collation_timer_;
// The report currently being assembled. This becomes non-NULL when an initial
// incident is reported, and returns to NULL when the report is sent for
@@ -258,9 +286,13 @@ class IncidentReportingService : public content::NotificationObserver {
// The time at which download collection was initiated.
base::TimeTicks last_download_begin_;
- // Context data for all on-the-record profiles.
+ // Context data for all on-the-record profiles plus the process-wide (NULL)
+ // context.
ProfileContextCollection profiles_;
+ // Callbacks registered for performing delayed analysis.
+ DelayedCallbackRunner delayed_analysis_callbacks_;
+
// The collection of uploads in progress.
ScopedVector<UploadContext> uploads_;
diff --git a/chrome/browser/safe_browsing/incident_reporting_service_unittest.cc b/chrome/browser/safe_browsing/incident_reporting_service_unittest.cc
index d3dcc19..903002a 100644
--- a/chrome/browser/safe_browsing/incident_reporting_service_unittest.cc
+++ b/chrome/browser/safe_browsing/incident_reporting_service_unittest.cc
@@ -56,7 +56,10 @@ class IncidentReportingServiceTest : public testing::Test {
const CollectEnvironmentCallback& collect_environment_callback,
const CreateDownloadFinderCallback& create_download_finder_callback,
const StartUploadCallback& start_upload_callback)
- : IncidentReportingService(NULL, NULL),
+ : IncidentReportingService(NULL,
+ NULL,
+ base::TimeDelta::FromMilliseconds(5),
+ task_runner),
pre_profile_add_callback_(pre_profile_add_callback),
collect_environment_callback_(collect_environment_callback),
create_download_finder_callback_(create_download_finder_callback),
@@ -132,6 +135,13 @@ class IncidentReportingServiceTest : public testing::Test {
ON_CREATE_DOWNLOAD_FINDER_NO_PROFILES,
};
+ // A type for specifying the action to be taken by the test fixture when its
+ // delayed analysis callback is run.
+ enum OnDelayedAnalysisAction {
+ ON_DELAYED_ANALYSIS_NO_ACTION,
+ ON_DELAYED_ANALYSIS_ADD_INCIDENT, // Add an incident to the service.
+ };
+
static const int64 kIncidentTimeMsec;
static const char kFakeOsName[];
static const char kFakeDownloadToken[];
@@ -153,11 +163,13 @@ class IncidentReportingServiceTest : public testing::Test {
base::Unretained(this)))),
on_create_download_finder_action_(
ON_CREATE_DOWNLOAD_FINDER_DOWNLOAD_FOUND),
+ on_delayed_analysis_action_(ON_DELAYED_ANALYSIS_NO_ACTION),
upload_result_(safe_browsing::IncidentReportUploader::UPLOAD_SUCCESS),
environment_collected_(),
download_finder_created_(),
download_finder_destroyed_(),
- uploader_destroyed_() {}
+ uploader_destroyed_(),
+ delayed_analysis_ran_() {}
virtual void SetUp() OVERRIDE {
testing::Test::SetUp();
@@ -224,6 +236,14 @@ class IncidentReportingServiceTest : public testing::Test {
instance_->GetAddIncidentCallback(profile).Run(MakeTestIncident().Pass());
}
+ // Registers the callback to be run for delayed analysis.
+ void RegisterAnalysis(OnDelayedAnalysisAction on_delayed_analysis_action) {
+ on_delayed_analysis_action_ = on_delayed_analysis_action;
+ instance_->RegisterDelayedAnalysisCallback(
+ base::Bind(&IncidentReportingServiceTest::OnDelayedAnalysis,
+ base::Unretained(this)));
+ }
+
// Confirms that the test incident(s) was/were uploaded by the service, then
// clears the instance for subsequent incidents.
void ExpectTestIncidentUploaded(int incident_count) {
@@ -256,6 +276,7 @@ class IncidentReportingServiceTest : public testing::Test {
bool HasCreatedDownloadFinder() const { return download_finder_created_; }
bool DownloadFinderDestroyed() const { return download_finder_destroyed_; }
bool UploaderDestroyed() const { return uploader_destroyed_; }
+ bool DelayedAnalysisRan() const { return delayed_analysis_ran_; }
scoped_refptr<base::TestSimpleTaskRunner> task_runner_;
base::ThreadTaskRunnerHandle thread_task_runner_handle_;
@@ -263,12 +284,14 @@ class IncidentReportingServiceTest : public testing::Test {
scoped_ptr<safe_browsing::IncidentReportingService> instance_;
base::Closure on_start_upload_callback_;
OnCreateDownloadFinderAction on_create_download_finder_action_;
+ OnDelayedAnalysisAction on_delayed_analysis_action_;
safe_browsing::IncidentReportUploader::Result upload_result_;
bool environment_collected_;
bool download_finder_created_;
scoped_ptr<safe_browsing::ClientIncidentReport> uploaded_report_;
bool download_finder_destroyed_;
bool uploader_destroyed_;
+ bool delayed_analysis_ran_;
private:
// A fake IncidentReportUploader that posts a task to provide a given response
@@ -435,6 +458,12 @@ class IncidentReportingServiceTest : public testing::Test {
void OnDownloadFinderDestroyed() { download_finder_destroyed_ = true; }
void OnUploaderDestroyed() { uploader_destroyed_ = true; }
+ void OnDelayedAnalysis(const safe_browsing::AddIncidentCallback& callback) {
+ delayed_analysis_ran_ = true;
+ if (on_delayed_analysis_action_ == ON_DELAYED_ANALYSIS_ADD_INCIDENT)
+ callback.Run(MakeTestIncident().Pass());
+ }
+
// A mapping of profile name to its corresponding properties.
std::map<std::string, ProfileProperties> profile_properties_;
};
@@ -680,6 +709,188 @@ TEST_F(IncidentReportingServiceTest, MigrateOldPref) {
AssertNoUpload();
}
+// Tests that no upload results from adding an incident that is not affiliated
+// with a profile.
+TEST_F(IncidentReportingServiceTest, ProcessWideNoProfileNoUpload) {
+ // Add the test incident.
+ AddTestIncident(NULL);
+
+ // Let all tasks run.
+ task_runner_->RunUntilIdle();
+
+ // No upload should have taken place.
+ AssertNoUpload();
+}
+
+// Tests that there is an upload when a profile is present for a proc-wide
+// incident and that pruning works.
+TEST_F(IncidentReportingServiceTest, ProcessWideOneUpload) {
+ // Add a profile that participates in safe browsing.
+ CreateProfile(
+ "profile1", SAFE_BROWSING_OPT_IN, ON_PROFILE_ADDITION_NO_ACTION);
+
+ // Add the test incident.
+ AddTestIncident(NULL);
+
+ // Let all tasks run.
+ task_runner_->RunUntilIdle();
+
+ // An upload should have taken place.
+ ExpectTestIncidentUploaded(1);
+
+ // Add the incident to the service again.
+ AddTestIncident(NULL);
+
+ // Let all tasks run.
+ task_runner_->RunUntilIdle();
+
+ // Verify that no additional report upload took place.
+ AssertNoUpload();
+}
+
+// Tests that two process-wide incidents of the same type with different
+// payloads added via the same callback lead to two uploads.
+TEST_F(IncidentReportingServiceTest, ProcessWideTwoUploads) {
+ // Add a profile that participates in safe browsing.
+ CreateProfile(
+ "profile1", SAFE_BROWSING_OPT_IN, ON_PROFILE_ADDITION_NO_ACTION);
+
+ // Add the test incident.
+ safe_browsing::AddIncidentCallback add_incident(
+ instance_->GetAddIncidentCallback(NULL));
+ add_incident.Run(MakeTestIncident().Pass());
+
+ // Let all tasks run.
+ task_runner_->RunUntilIdle();
+
+ // An upload should have taken place.
+ ExpectTestIncidentUploaded(1);
+
+ // Add a variation on the incident to the service.
+ scoped_ptr<safe_browsing::ClientIncidentReport_IncidentData> incident(
+ MakeTestIncident());
+ incident->mutable_tracked_preference()->set_atomic_value("leeches");
+ add_incident.Run(incident.Pass());
+
+ // Let all tasks run.
+ task_runner_->RunUntilIdle();
+
+ // Verify that an additional report upload took place.
+ ExpectTestIncidentUploaded(1);
+}
+
+// Tests that there is an upload when a profile appears after a proc-wide
+// incident.
+TEST_F(IncidentReportingServiceTest, ProcessWideOneUploadAfterProfile) {
+ // Add the test incident.
+ AddTestIncident(NULL);
+
+ // Let all tasks run.
+ task_runner_->RunUntilIdle();
+
+ // Verify that no report upload took place.
+ AssertNoUpload();
+
+ // Add a profile that participates in safe browsing.
+ CreateProfile(
+ "profile1", SAFE_BROWSING_OPT_IN, ON_PROFILE_ADDITION_NO_ACTION);
+
+ // Let all tasks run.
+ task_runner_->RunUntilIdle();
+
+ // An upload should have taken place.
+ ExpectTestIncidentUploaded(1);
+}
+
+// Tests that delayed analysis callbacks are called following the addition of a
+// profile that participates in safe browsing.
+TEST_F(IncidentReportingServiceTest, AnalysisAfterProfile) {
+ // Register a callback.
+ RegisterAnalysis(ON_DELAYED_ANALYSIS_NO_ACTION);
+
+ // Let all tasks run.
+ task_runner_->RunUntilIdle();
+
+ // Not run yet.
+ ASSERT_FALSE(DelayedAnalysisRan());
+
+ // Add a profile that participates in safe browsing.
+ CreateProfile(
+ "profile1", SAFE_BROWSING_OPT_IN, ON_PROFILE_ADDITION_NO_ACTION);
+
+ // Let all tasks run.
+ task_runner_->RunUntilIdle();
+
+ // And now they have.
+ ASSERT_TRUE(DelayedAnalysisRan());
+}
+
+// Tests that delayed analysis callbacks are called following their registration
+// when a profile that participates in safe browsing is already present.
+TEST_F(IncidentReportingServiceTest, AnalysisWhenRegisteredWithProfile) {
+ // Add a profile that participates in safe browsing.
+ CreateProfile(
+ "profile1", SAFE_BROWSING_OPT_IN, ON_PROFILE_ADDITION_NO_ACTION);
+
+ // Register a callback.
+ RegisterAnalysis(ON_DELAYED_ANALYSIS_NO_ACTION);
+
+ // Let all tasks run.
+ task_runner_->RunUntilIdle();
+
+ // Confirm that the callbacks were run.
+ ASSERT_TRUE(DelayedAnalysisRan());
+}
+
+// Tests that no upload results from a delayed analysis incident when no
+// safe browsing profile is present.
+TEST_F(IncidentReportingServiceTest, DelayedAnalysisNoProfileNoUpload) {
+ // Register a callback that will add an incident.
+ RegisterAnalysis(ON_DELAYED_ANALYSIS_ADD_INCIDENT);
+
+ // Add a profile that does not participate in safe browsing.
+ CreateProfile(
+ "profile1", SAFE_BROWSING_OPT_OUT, ON_PROFILE_ADDITION_NO_ACTION);
+
+ // Let all tasks run.
+ task_runner_->RunUntilIdle();
+
+ // The callback should not have been run.
+ ASSERT_FALSE(DelayedAnalysisRan());
+
+ // No upload should have taken place.
+ AssertNoUpload();
+}
+
+// Tests that there is an upload when a profile is present for a delayed
+// analysis incident and that pruning works.
+TEST_F(IncidentReportingServiceTest, DelayedAnalysisOneUpload) {
+ // Register a callback that will add an incident.
+ RegisterAnalysis(ON_DELAYED_ANALYSIS_ADD_INCIDENT);
+
+ // Add a profile that participates in safe browsing.
+ CreateProfile(
+ "profile1", SAFE_BROWSING_OPT_IN, ON_PROFILE_ADDITION_NO_ACTION);
+
+ // Let all tasks run.
+ task_runner_->RunUntilIdle();
+
+ // The callback should have been run.
+ ASSERT_TRUE(DelayedAnalysisRan());
+
+ // An upload should have taken place.
+ ExpectTestIncidentUploaded(1);
+
+ // Add the incident to the service again.
+ AddTestIncident(NULL);
+
+ // Let all tasks run.
+ task_runner_->RunUntilIdle();
+
+ // Verify that no additional report upload took place.
+ AssertNoUpload();
+}
+
// Parallel uploads
// Shutdown during processing
// environment colection taking longer than incident delay timer
diff --git a/chrome/browser/safe_browsing/safe_browsing_service.cc b/chrome/browser/safe_browsing/safe_browsing_service.cc
index 277b910..71b7b13 100644
--- a/chrome/browser/safe_browsing/safe_browsing_service.cc
+++ b/chrome/browser/safe_browsing/safe_browsing_service.cc
@@ -332,6 +332,14 @@ SafeBrowsingService::CreatePreferenceValidationDelegate(
return scoped_ptr<TrackedPreferenceValidationDelegate>();
}
+void SafeBrowsingService::RegisterDelayedAnalysisCallback(
+ const safe_browsing::DelayedAnalysisCallback& callback) {
+#if defined(FULL_SAFE_BROWSING)
+ if (incident_service_)
+ incident_service_->RegisterDelayedAnalysisCallback(callback);
+#endif
+}
+
SafeBrowsingUIManager* SafeBrowsingService::CreateUIManager() {
return new SafeBrowsingUIManager(this);
}
diff --git a/chrome/browser/safe_browsing/safe_browsing_service.h b/chrome/browser/safe_browsing/safe_browsing_service.h
index 2fbfbc3..6254d51 100644
--- a/chrome/browser/safe_browsing/safe_browsing_service.h
+++ b/chrome/browser/safe_browsing/safe_browsing_service.h
@@ -17,6 +17,7 @@
#include "base/memory/scoped_ptr.h"
#include "base/observer_list.h"
#include "base/sequenced_task_runner_helpers.h"
+#include "chrome/browser/safe_browsing/delayed_analysis_callback.h"
#include "chrome/browser/safe_browsing/safe_browsing_util.h"
#include "content/public/browser/browser_thread.h"
#include "content/public/browser/notification_observer.h"
@@ -124,6 +125,12 @@ class SafeBrowsingService
scoped_ptr<TrackedPreferenceValidationDelegate>
CreatePreferenceValidationDelegate(Profile* profile) const;
+ // Registers |callback| to be run after some delay following process launch.
+ // |callback| will be dropped if the service is not applicable for the
+ // process.
+ void RegisterDelayedAnalysisCallback(
+ const safe_browsing::DelayedAnalysisCallback& callback);
+
protected:
// Creates the safe browsing service. Need to initialize before using.
SafeBrowsingService();
diff --git a/chrome/chrome_browser.gypi b/chrome/chrome_browser.gypi
index 5bbcd0b..8986b5a 100644
--- a/chrome/chrome_browser.gypi
+++ b/chrome/chrome_browser.gypi
@@ -2653,6 +2653,9 @@
'browser/safe_browsing/client_side_detection_service.h',
'browser/safe_browsing/database_manager.cc',
'browser/safe_browsing/database_manager.h',
+ 'browser/safe_browsing/delayed_analysis_callback.h',
+ 'browser/safe_browsing/delayed_callback_runner.cc',
+ 'browser/safe_browsing/delayed_callback_runner.h',
'browser/safe_browsing/download_feedback.cc',
'browser/safe_browsing/download_feedback.h',
'browser/safe_browsing/download_feedback_service.cc',
diff --git a/chrome/chrome_tests_unit.gypi b/chrome/chrome_tests_unit.gypi
index 900e9d9..858e3e6 100644
--- a/chrome/chrome_tests_unit.gypi
+++ b/chrome/chrome_tests_unit.gypi
@@ -1205,6 +1205,7 @@
'browser/safe_browsing/chunk_range_unittest.cc',
'browser/safe_browsing/client_side_detection_host_unittest.cc',
'browser/safe_browsing/client_side_detection_service_unittest.cc',
+ 'browser/safe_browsing/delayed_callback_runner_unittest.cc',
'browser/safe_browsing/database_manager_unittest.cc',
'browser/safe_browsing/download_feedback_service_unittest.cc',
'browser/safe_browsing/download_feedback_unittest.cc',