chromium/ash/webui/diagnostics_ui/diagnostics_metrics_message_handler.cc

// 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