| Index: chrome/browser/safe_browsing/srt_fetcher_win.cc
|
| diff --git a/chrome/browser/safe_browsing/srt_fetcher_win.cc b/chrome/browser/safe_browsing/srt_fetcher_win.cc
|
| index c8733099af9c188aaf64d3e1d6d02524c369acfd..01fb75c9e6905cfc81c28124da94a55e3c9fb9ff 100644
|
| --- a/chrome/browser/safe_browsing/srt_fetcher_win.cc
|
| +++ b/chrome/browser/safe_browsing/srt_fetcher_win.cc
|
| @@ -15,6 +15,7 @@
|
| #include "base/bind_helpers.h"
|
| #include "base/callback_helpers.h"
|
| #include "base/command_line.h"
|
| +#include "base/debug/leak_annotations.h"
|
| #include "base/files/file_path.h"
|
| #include "base/macros.h"
|
| #include "base/metrics/field_trial.h"
|
| @@ -129,7 +130,9 @@ const char kMemoryUsedMetricName[] = "SoftwareReporter.MemoryUsed";
|
| // Reports metrics about the software reporter via UMA (and sometimes Rappor).
|
| class UMAHistogramReporter {
|
| public:
|
| - explicit UMAHistogramReporter(const std::string& suffix = std::string())
|
| + UMAHistogramReporter() : UMAHistogramReporter(std::string()) {}
|
| +
|
| + explicit UMAHistogramReporter(const std::string& suffix)
|
| : suffix_(suffix),
|
| registry_key_(suffix.empty() ? kSoftwareRemovalToolRegistryKey
|
| : base::StringPrintf(
|
| @@ -225,6 +228,8 @@ class UMAHistogramReporter {
|
| RecordLongTimesHistogram("SoftwareReporter.RunningTimeAccordingToChrome",
|
| reporter_running_time);
|
|
|
| + // TODO(b/641081): This should only have KEY_QUERY_VALUE and KEY_SET_VALUE,
|
| + // and use Open to avoid creating the key if it doesn't already exist.
|
| base::win::RegKey reporter_key(HKEY_CURRENT_USER, registry_key_.c_str(),
|
| KEY_ALL_ACCESS);
|
| if (!reporter_key.Valid()) {
|
| @@ -282,6 +287,8 @@ class UMAHistogramReporter {
|
| void ReportScanTimes() const {
|
| base::string16 scan_times_key_path = base::StringPrintf(
|
| L"%ls\\%ls", registry_key_.c_str(), kScanTimesSubKey);
|
| + // TODO(b/641081): This should only have KEY_QUERY_VALUE and KEY_SET_VALUE,
|
| + // and use Open to avoid creating the key if it doesn't already exist.
|
| base::win::RegKey scan_times_key(
|
| HKEY_CURRENT_USER, scan_times_key_path.c_str(), KEY_ALL_ACCESS);
|
| if (!scan_times_key.Valid())
|
| @@ -608,26 +615,32 @@ void MaybeFetchSRT(Browser* browser, const base::Version& reporter_version) {
|
| new SRTFetcher(profile);
|
| }
|
|
|
| -// This class tries to run the reporter and reacts to its exit code. It
|
| -// schedules subsequent runs as needed, or retries as soon as a browser is
|
| -// available when none is on first try.
|
| +// This class tries to run a queue of reporters and react to their exit codes.
|
| +// It schedules subsequent runs of the queue as needed, or retries as soon as a
|
| +// browser is available when none is on first try.
|
| class ReporterRunner : public chrome::BrowserListObserver {
|
| public:
|
| - // Starts the sequence of attempts to run the reporter.
|
| - static void Run(const SwReporterInvocation& invocation,
|
| - const base::Version& version,
|
| - scoped_refptr<base::TaskRunner> main_thread_task_runner,
|
| - scoped_refptr<base::TaskRunner> blocking_task_runner) {
|
| - if (!instance_)
|
| + // Registers |invocations| to run next time |TryToRun| is scheduled. (And if
|
| + // it's not already scheduled, call it now.)
|
| + static void ScheduleInvocations(
|
| + const SwReporterQueue& invocations,
|
| + const base::Version& version,
|
| + scoped_refptr<base::TaskRunner> main_thread_task_runner,
|
| + scoped_refptr<base::TaskRunner> blocking_task_runner) {
|
| + if (!instance_) {
|
| instance_ = new ReporterRunner;
|
| + ANNOTATE_LEAKING_OBJECT_PTR(instance_);
|
| + }
|
| DCHECK_CURRENTLY_ON(BrowserThread::UI);
|
| - // There's nothing to do if the path and version of the reporter has not
|
| - // changed, we just keep running the tasks that are running now.
|
| - if (instance_->invocation_ == invocation && instance_->version_.IsValid() &&
|
| - instance_->version_ == version)
|
| +
|
| + // There's nothing to do if the invocation parameters and version of the
|
| + // reporter have not changed, we just keep running the tasks that are
|
| + // running now.
|
| + if (instance_->pending_invocations_ == invocations &&
|
| + instance_->version_.IsValid() && instance_->version_ == version)
|
| return;
|
|
|
| - instance_->invocation_ = invocation;
|
| + instance_->pending_invocations_ = invocations;
|
| instance_->version_ = version;
|
| instance_->main_thread_task_runner_ = std::move(main_thread_task_runner);
|
| instance_->blocking_task_runner_ = std::move(blocking_task_runner);
|
| @@ -652,9 +665,48 @@ class ReporterRunner : public chrome::BrowserListObserver {
|
| BrowserList::RemoveObserver(this);
|
| }
|
|
|
| - // This method is called on the UI thread when the reporter run has completed.
|
| - // This is run as a task posted from an interruptible worker thread so should
|
| - // be resilient to unexpected shutdown.
|
| + // Launches the command line at the head of the queue.
|
| + void ScheduleNextInvocation() {
|
| + DCHECK(!current_invocations_.empty());
|
| + auto next_invocation = current_invocations_.front();
|
| + current_invocations_.pop();
|
| +
|
| + if (g_testing_delegate_)
|
| + g_testing_delegate_->NotifyLaunchReady();
|
| +
|
| + // Add switches for users who opted into extended Safe Browsing reporting.
|
| + // The invocation object is changed locally right before the actual process
|
| + // is launched because user status can change between this and the next run
|
| + // for this ReporterRunner object. For example, the ReporterDone() callback
|
| + // schedules the next run for a few days later, and the user might have
|
| + // changed settings in the meantime.
|
| + PrefService* local_state = g_browser_process->local_state();
|
| + if (next_invocation.flags & SwReporterInvocation::FLAG_SEND_REPORTER_LOGS &&
|
| + local_state && ShouldSendReporterLogs(*local_state)) {
|
| + AddSwitchesForExtendedReporterUser(&next_invocation);
|
| + // Set the local state value before the first attempt to run the
|
| + // reporter, because we only want to upload logs once in the window
|
| + // defined by |kDaysBetweenReporterLogsSent|. If we set with other local
|
| + // state values after the reporter runs, we could send logs again too
|
| + // quickly (for example, if Chrome stops before the reporter finishes).
|
| + local_state->SetInt64(prefs::kSwReporterLastTimeSentReport,
|
| + base::Time::Now().ToInternalValue());
|
| + }
|
| +
|
| + // It's OK to simply |PostTaskAndReplyWithResult| so that
|
| + // |LaunchAndWaitForExit| doesn't need to access |main_thread_task_runner_|
|
| + // since the callback is not delayed and the test task runner won't need to
|
| + // force it.
|
| + base::PostTaskAndReplyWithResult(
|
| + blocking_task_runner_.get(), FROM_HERE,
|
| + base::Bind(&LaunchAndWaitForExit, next_invocation),
|
| + base::Bind(&ReporterRunner::ReporterDone, base::Unretained(this),
|
| + base::Time::Now(), version_, next_invocation));
|
| + }
|
| +
|
| + // This method is called on the UI thread when an invocation of the reporter
|
| + // has completed. This is run as a task posted from an interruptible worker
|
| + // thread so should be resilient to unexpected shutdown.
|
| void ReporterDone(const base::Time& reporter_start_time,
|
| const base::Version& version,
|
| const SwReporterInvocation& finished_invocation,
|
| @@ -666,25 +718,41 @@ class ReporterRunner : public chrome::BrowserListObserver {
|
|
|
| base::TimeDelta reporter_running_time =
|
| base::Time::Now() - reporter_start_time;
|
| - // Don't continue when the reporter process failed to launch, but still try
|
| - // again after the regular delay. It's not worth retrying earlier, risking
|
| - // running too often if it always fails, since not many users fail here.
|
| - main_thread_task_runner_->PostDelayedTask(
|
| - FROM_HERE,
|
| - base::Bind(&ReporterRunner::TryToRun, base::Unretained(this)),
|
| - base::TimeDelta::FromDays(days_between_reporter_runs_));
|
| +
|
| + // Don't continue the current queue of reporters if one failed to launch.
|
| + if (exit_code == kReporterFailureExitCode)
|
| + current_invocations_ = SwReporterQueue();
|
| +
|
| + // As soon as we're not running this queue, schedule the next overall queue
|
| + // run after the regular delay. (If there was a failure it's not worth
|
| + // retrying earlier, risking running too often if it always fails, since
|
| + // not many users fail here.)
|
| + if (current_invocations_.empty()) {
|
| + main_thread_task_runner_->PostDelayedTask(
|
| + FROM_HERE,
|
| + base::Bind(&ReporterRunner::TryToRun, base::Unretained(this)),
|
| + base::TimeDelta::FromDays(days_between_reporter_runs_));
|
| + } else {
|
| + ScheduleNextInvocation();
|
| + }
|
| +
|
| + // If the reporter failed to launch, do not process the results. (The exit
|
| + // code itself doesn't need to be logged in this case because
|
| + // SW_REPORTER_FAILED_TO_START is logged in |LaunchAndWaitForExit|.)
|
| if (exit_code == kReporterFailureExitCode)
|
| return;
|
|
|
| UMAHistogramReporter uma(finished_invocation.suffix);
|
| uma.ReportVersion(version);
|
| uma.ReportExitCode(exit_code);
|
| - uma.ReportFoundUwS(!finished_invocation.is_experimental /*use_rappor*/);
|
| + uma.ReportFoundUwS(finished_invocation.flags &
|
| + SwReporterInvocation::FLAG_LOG_TO_RAPPOR);
|
|
|
| - // Only save results from the canonical version of the software.
|
| PrefService* local_state = g_browser_process->local_state();
|
| - if (local_state && !finished_invocation.is_experimental) {
|
| - local_state->SetInteger(prefs::kSwReporterLastExitCode, exit_code);
|
| + if (local_state) {
|
| + if (finished_invocation.flags &
|
| + SwReporterInvocation::FLAG_LOG_EXIT_CODE_TO_PREFS)
|
| + local_state->SetInteger(prefs::kSwReporterLastExitCode, exit_code);
|
| local_state->SetInt64(prefs::kSwReporterLastTimeTriggered,
|
| base::Time::Now().ToInternalValue());
|
| }
|
| @@ -692,8 +760,8 @@ class ReporterRunner : public chrome::BrowserListObserver {
|
| uma.ReportScanTimes();
|
| uma.ReportMemoryUsage();
|
|
|
| - // Only continue to launch the prompt for the canonical version.
|
| - if (finished_invocation.is_experimental)
|
| + if (!(finished_invocation.flags &
|
| + SwReporterInvocation::FLAG_TRIGGER_PROMPT))
|
| return;
|
|
|
| if (!IsInSRTPromptFieldTrialGroups()) {
|
| @@ -727,12 +795,17 @@ class ReporterRunner : public chrome::BrowserListObserver {
|
| void TryToRun() {
|
| DCHECK_CURRENTLY_ON(BrowserThread::UI);
|
| PrefService* local_state = g_browser_process->local_state();
|
| +
|
| if (!version_.IsValid() || !local_state) {
|
| + // TODO(b/641081): This doesn't look right. Even on first run, |version_|
|
| + // should be valid (and this is already checked in RunSwReporters). We
|
| + // should abort if local_state is missing, but this has nothing to do
|
| + // with |first_run_|.
|
| DCHECK(first_run_);
|
| return;
|
| }
|
|
|
| - // Run the reporter if it hasn't been triggered in the last
|
| + // Run a queue of reporters if none have been triggered in the last
|
| // |days_between_reporter_runs_| days, which depends if there is a pending
|
| // prompt to be added to Chrome's menu.
|
| if (local_state->GetBoolean(prefs::kSwReporterPendingPrompt)) {
|
| @@ -741,51 +814,25 @@ class ReporterRunner : public chrome::BrowserListObserver {
|
| } else {
|
| days_between_reporter_runs_ = kDaysBetweenSuccessfulSwReporterRuns;
|
| }
|
| + const base::Time now = base::Time::Now();
|
| const base::Time last_time_triggered = base::Time::FromInternalValue(
|
| local_state->GetInt64(prefs::kSwReporterLastTimeTriggered));
|
| - base::TimeDelta next_trigger_delay(
|
| + const base::Time next_trigger(
|
| last_time_triggered +
|
| - base::TimeDelta::FromDays(days_between_reporter_runs_) -
|
| - base::Time::Now());
|
| - if (next_trigger_delay.ToInternalValue() <= 0 ||
|
| - // Also make sure the kSwReporterLastTimeTriggered value is not set in
|
| - // the future.
|
| - last_time_triggered > base::Time::Now()) {
|
| - if (g_testing_delegate_)
|
| - g_testing_delegate_->NotifyLaunchReady();
|
| -
|
| - // Creating a new invocation to add switches for users who opted into
|
| - // extended Safe Browsing reporting. The invocation object is changed
|
| - // locally right before the actual process is launched because user status
|
| - // can change between this and the next run for this ReporterRunner
|
| - // object. For example, the ReporterDone() callback schedules the next run
|
| - // for a few days later, and the user might have changed settings in the
|
| - // meantime.
|
| - SwReporterInvocation actual_invocation(invocation_);
|
| - if (ShouldSendReporterLogs(*local_state)) {
|
| - AddSwitchesForExtendedReporterUser(&actual_invocation);
|
| - // Set the local state value before the first attempt to run the
|
| - // reporter, because we only want to upload logs once in the window
|
| - // defined by |kDaysBetweenReporterLogsSent|. If we set with other local
|
| - // state values after the reporter runs, we could send logs again too
|
| - // quickly (for example, if Chrome stops before the reporter finishes).
|
| - local_state->SetInt64(prefs::kSwReporterLastTimeSentReport,
|
| - base::Time::Now().ToInternalValue());
|
| - }
|
| - // It's OK to simply |PostTaskAndReplyWithResult| so that
|
| - // |LaunchAndWaitForExit| doesn't need to access
|
| - // |main_thread_task_runner_| since the callback is not delayed and the
|
| - // test task runner won't need to force it.
|
| - base::PostTaskAndReplyWithResult(
|
| - blocking_task_runner_.get(), FROM_HERE,
|
| - base::Bind(&LaunchAndWaitForExit, actual_invocation),
|
| - base::Bind(&ReporterRunner::ReporterDone, base::Unretained(this),
|
| - base::Time::Now(), version_, invocation_));
|
| + base::TimeDelta::FromDays(days_between_reporter_runs_));
|
| + if (!pending_invocations_.empty() &&
|
| + (next_trigger <= now ||
|
| + // Also make sure the kSwReporterLastTimeTriggered value is not set in
|
| + // the future.
|
| + last_time_triggered > now)) {
|
| + DCHECK(current_invocations_.empty());
|
| + current_invocations_ = pending_invocations_;
|
| + ScheduleNextInvocation();
|
| } else {
|
| main_thread_task_runner_->PostDelayedTask(
|
| FROM_HERE,
|
| base::Bind(&ReporterRunner::TryToRun, base::Unretained(this)),
|
| - next_trigger_delay);
|
| + next_trigger - now);
|
| }
|
| }
|
|
|
| @@ -821,15 +868,21 @@ class ReporterRunner : public chrome::BrowserListObserver {
|
| }
|
|
|
| bool first_run_ = true;
|
| - SwReporterInvocation invocation_;
|
| +
|
| + // The queue of invocations that are currently running.
|
| + SwReporterQueue current_invocations_;
|
| +
|
| + // The invocations to run next time the SwReporter is run.
|
| + SwReporterQueue pending_invocations_;
|
| +
|
| base::Version version_;
|
| scoped_refptr<base::TaskRunner> main_thread_task_runner_;
|
| scoped_refptr<base::TaskRunner> blocking_task_runner_;
|
|
|
| // This value is used to identify how long to wait before starting a new run
|
| - // of the reporter. It's initialized with the default value and may be changed
|
| - // to a different value when a prompt is pending and the reporter should be
|
| - // run before adding the global error to the Chrome menu.
|
| + // of the reporter queue. It's initialized with the default value and may be
|
| + // changed to a different value when a prompt is pending and the reporter
|
| + // should be run before adding the global error to the Chrome menu.
|
| int days_between_reporter_runs_ = kDaysBetweenSuccessfulSwReporterRuns;
|
|
|
| // A single leaky instance.
|
| @@ -845,11 +898,6 @@ ReporterRunner* ReporterRunner::instance_ = nullptr;
|
| SwReporterInvocation::SwReporterInvocation()
|
| : command_line(base::CommandLine::NO_PROGRAM) {}
|
|
|
| -SwReporterInvocation::SwReporterInvocation(const SwReporterInvocation& other)
|
| - : command_line(other.command_line),
|
| - suffix(other.suffix),
|
| - is_experimental(other.is_experimental) {}
|
| -
|
| SwReporterInvocation SwReporterInvocation::FromFilePath(
|
| const base::FilePath& exe_path) {
|
| SwReporterInvocation invocation;
|
| @@ -866,15 +914,18 @@ SwReporterInvocation SwReporterInvocation::FromCommandLine(
|
|
|
| bool SwReporterInvocation::operator==(const SwReporterInvocation& other) const {
|
| return command_line.argv() == other.command_line.argv() &&
|
| - suffix == other.suffix && is_experimental == other.is_experimental;
|
| + suffix == other.suffix && flags == other.flags;
|
| }
|
|
|
| -void RunSwReporter(const SwReporterInvocation& invocation,
|
| - const base::Version& version,
|
| - scoped_refptr<base::TaskRunner> main_thread_task_runner,
|
| - scoped_refptr<base::TaskRunner> blocking_task_runner) {
|
| - ReporterRunner::Run(invocation, version, std::move(main_thread_task_runner),
|
| - std::move(blocking_task_runner));
|
| +void RunSwReporters(const SwReporterQueue& invocations,
|
| + const base::Version& version,
|
| + scoped_refptr<base::TaskRunner> main_thread_task_runner,
|
| + scoped_refptr<base::TaskRunner> blocking_task_runner) {
|
| + DCHECK(!invocations.empty());
|
| + DCHECK(version.IsValid());
|
| + ReporterRunner::ScheduleInvocations(invocations, version,
|
| + std::move(main_thread_task_runner),
|
| + std::move(blocking_task_runner));
|
| }
|
|
|
| bool ReporterFoundUws() {
|
|
|