1
    2
    3
    4
    5
    6
    7
    8
    9
   10
   11
   12
   13
   14
   15
   16
   17
   18
   19
   20
   21
   22
   23
   24
   25
   26
   27
   28
   29
   30
   31
   32
   33
   34
   35
   36
   37
   38
   39
   40
   41
   42
   43
   44
   45
   46
   47
   48
   49
   50
   51
   52
   53
   54
   55
   56
   57
   58
   59
   60
   61
   62
   63
   64
   65
   66
   67
   68
   69
   70
   71
   72
   73
   74
   75
   76
   77
   78
   79
   80
   81
   82
   83
   84
   85
   86
   87
   88
   89
   90
   91
   92
   93
   94
   95
   96
   97
   98
   99
  100
  101
  102
  103
  104
  105
  106
  107
  108
  109
  110
  111
  112
  113
  114
  115
  116
  117
  118
  119
  120
  121
  122
  123
  124
  125
  126

ash / webui / diagnostics_ui / diagnostics_metrics_message_handler.cc [blame]

// Copyright 2021 The Chromium Authors
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.

#include "ash/webui/diagnostics_ui/diagnostics_metrics_message_handler.h"

#include <string_view>

#include "base/check.h"
#include "base/check_op.h"
#include "base/containers/fixed_flat_map.h"
#include "base/containers/flat_map.h"
#include "base/functional/bind.h"
#include "base/metrics/histogram_functions.h"
#include "base/notreached.h"
#include "base/time/time.h"
#include "content/public/browser/web_ui.h"

namespace ash {
namespace diagnostics {
namespace metrics {
namespace {

// Handler names:
const char kRecordNavigation[] = "recordNavigation";

// Checks Javascript input integrity before parsing.
bool IsValidNavigationViewValue(const base::Value& value) {
  return value.is_int() && value.GetInt() >= 0 &&
         value.GetInt() <= static_cast<int>(NavigationView::kMaxValue);
}

// Converts base::Value<int> to NavigationView based on enum values.
NavigationView ConvertToNavigationView(const base::Value& value) {
  DCHECK(IsValidNavigationViewValue(value));

  return static_cast<NavigationView>(value.GetInt());
}

void EmitScreenOpenDuration(const NavigationView screen,
                            const base::TimeDelta& time_elapsed) {
  // Map of screens within Diagnostics app to matching duration metric name.
  constexpr auto kOpenDurationMetrics =
      base::MakeFixedFlatMap<NavigationView, std::string_view>({
          {NavigationView::kConnectivity,
           "ChromeOS.DiagnosticsUi.Connectivity.OpenDuration"},
          {NavigationView::kInput, "ChromeOS.DiagnosticsUi.Input.OpenDuration"},
          {NavigationView::kSystem,
           "ChromeOS.DiagnosticsUi.System.OpenDuration"},
      });

  auto iter = kOpenDurationMetrics.find(screen);
  if (iter == kOpenDurationMetrics.end()) {
    NOTREACHED() << "Unknown NavigationView requested";
  }

  base::UmaHistogramLongTimes100(iter->second, time_elapsed);
}

}  // namespace

DiagnosticsMetricsMessageHandler::DiagnosticsMetricsMessageHandler(
    NavigationView initial_view)
    : current_view_(initial_view) {
  navigation_started_ = base::Time::Now();
}

DiagnosticsMetricsMessageHandler::~DiagnosticsMetricsMessageHandler() {
  // Emit final navigation event.
  EmitScreenOpenDuration(current_view_,
                         base::Time::Now() - navigation_started_);
}

// content::WebUIMessageHandler:
void DiagnosticsMetricsMessageHandler::RegisterMessages() {
  DCHECK(web_ui());

  web_ui()->RegisterMessageCallback(
      kRecordNavigation,
      base::BindRepeating(
          &DiagnosticsMetricsMessageHandler::HandleRecordNavigation,
          base::Unretained(this)));
}

// Test helpers:
NavigationView DiagnosticsMetricsMessageHandler::GetCurrentViewForTesting() {
  return current_view_;
}

base::TimeDelta
DiagnosticsMetricsMessageHandler::GetElapsedNavigationTimeDeltaForTesting() {
  return base::Time::Now() - navigation_started_;
}

void DiagnosticsMetricsMessageHandler::SetWebUiForTesting(
    content::WebUI* web_ui) {
  DCHECK(web_ui);

  set_web_ui(web_ui);
}

// Private:

// Message Handlers:
void DiagnosticsMetricsMessageHandler::HandleRecordNavigation(
    const base::Value::List& args) {
  // Ensure JS arguments received are valid before using in calls to metrics.
  if (args.size() != 2u || !IsValidNavigationViewValue(args[0]) ||
      !IsValidNavigationViewValue(args[1]) || args[0] == args[1]) {
    return;
  }

  const NavigationView from_view = ConvertToNavigationView(args[0]);
  const NavigationView to_view = ConvertToNavigationView(args[1]);
  const base::Time updated_start_time = base::Time::Now();

  // Recordable navigation event occurred.
  EmitScreenOpenDuration(from_view, updated_start_time - navigation_started_);

  // `current_view_` updated to recorded `to_view` and reset timer.
  current_view_ = to_view;
  navigation_started_ = updated_start_time;
}
}  // namespace metrics
}  // namespace diagnostics
}  // namespace ash