blob: 1526ab2b3b9affe3a83db7d8e4eed9a9d956daaa [file] [log] [blame]
// Copyright 2025 The Chromium Authors
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#ifndef CHROME_BROWSER_GLIC_GLIC_METRICS_H_
#define CHROME_BROWSER_GLIC_GLIC_METRICS_H_
#include <memory>
#include <set>
#include <vector>
#include "base/callback_list.h"
#include "base/memory/raw_ptr.h"
#include "base/time/time.h"
#include "base/timer/timer.h"
#include "chrome/browser/glic/host/context/glic_tab_data.h"
#include "chrome/browser/glic/host/glic.mojom.h"
#include "components/prefs/pref_change_registrar.h"
#include "services/metrics/public/cpp/ukm_source_id.h"
#include "ui/display/display.h"
class Profile;
class Browser;
namespace glic {
class GlicEnabling;
// These values are persisted to logs. Entries should not be renumbered and
// numeric values should never be reused.
// LINT.IfChange(ChromeRelativePosition)
enum class ChromeRelativePosition {
kAboveLeft = 0,
kCenterLeft = 1,
kBelowLeft = 2,
kAboveCenter = 3,
kOverlap = 4,
kBelowCenter = 5,
kAboveRight = 6,
kCenterRight = 7,
kBelowRight = 8,
kChromeOnOtherDisplay = 9,
kNoVisibleChromeBrowser = 10,
kMaxValue = kNoVisibleChromeBrowser,
};
// LINT.ThenChange(//tools/metrics/histograms/metadata/glic/enums.xml:ChromeRelativePosition)
// LINT.IfChange(DisplayPosition)
enum class DisplayPosition {
kTopLeft = 0,
kCenterLeft = 1,
kBottomLeft = 2,
kTopCenter = 3,
kCenterCenter = 4,
kBottomCenter = 5,
kTopRight = 6,
kCenterRight = 7,
kBottomRight = 8,
kUnknown = 9,
kMaxValue = kUnknown,
};
// LINT.ThenChange(//tools/metrics/histograms/metadata/glic/enums.xml:DisplayPosition)
// LINT.IfChange(PercentOverlap)
enum class PercentOverlap {
k0 = 0,
k10 = 1,
k20 = 2,
k30 = 3,
k40 = 4,
k50 = 5,
k60 = 6,
k70 = 7,
k80 = 8,
k90 = 9,
k100 = 10,
kNoVisibleChromeBrowser = 11,
kMaxValue = kNoVisibleChromeBrowser,
};
// LINT.ThenChange(//tools/metrics/histograms/metadata/glic/enums.xml:PercentOverlap)
// LINT.IfChange(Error)
enum class Error {
kResponseStartWithoutInput = 0,
kResponseStopWithoutInput = 1,
kResponseStartWhileHidingOrHidden = 2,
kWindowCloseWithoutWindowOpen = 3,
kMaxValue = kWindowCloseWithoutWindowOpen,
};
// LINT.ThenChange(//tools/metrics/histograms/metadata/glic/enums.xml:GlicResponseError)
// LINT.IfChange(EntryPointStatus)
enum class EntryPointStatus {
kBeforeFreNotEligible = 0,
kBeforeFreAndEligible = 1,
kIncompleteFreNotEligible = 2,
kIncompleteFreAndEligible = 3,
kAfterFreBrowserOnly = 4,
kAfterFreOsOnly = 5,
kAfterFreBrowserAndOs = 6,
kAfterFreThreeDotOnly = 7,
kAfterFreNotEligible = 8,
kMaxValue = kAfterFreNotEligible,
};
// LINT.ThenChange(//tools/metrics/histograms/metadata/glic/enums.xml:GlicEntryPointStatus)
// LINT.IfChange(ResponseSegmentation)
enum class ResponseSegmentation {
kUnknown = 0,
kOsButtonAttachedText = 1,
kOsButtonAttachedAudio = 2,
kOsButtonDetachedText = 3,
kOsButtonDetachedAudio = 4,
kOsButtonMenuAttachedText = 5,
kOsButtonMenuAttachedAudio = 6,
kOsButtonMenuDetachedText = 7,
kOsButtonMenuDetachedAudio = 8,
kOsHotkeyAttachedText = 9,
kOsHotkeyAttachedAudio = 10,
kOsHotkeyDetachedText = 11,
kOsHotkeyDetachedAudio = 12,
kButtonTopChromeAttachedText = 13,
kButtonTopChromeAttachedAudio = 14,
kButtonTopChromeDetachedText = 15,
kButtonTopChromeDetachedAudio = 16,
kFreAttachedText = 17,
kFreAttachedAudio = 18,
kFreDetachedText = 19,
kFreDetachedAudio = 20,
kProfilePickerAttachedText = 21,
kProfilePickerAttachedAudio = 22,
kProfilePickerDetachedText = 23,
kProfilePickerDetachedAudio = 24,
kNudgeAttachedText = 25,
kNudgeAttachedAudio = 26,
kNudgeDetachedText = 27,
kNudgeDetachedAudio = 28,
kThreeDotsMenuAttachedText = 29,
kThreeDotsMenuAttachedAudio = 30,
kThreeDotsMenuDetachedText = 31,
kThreeDotsMenuDetachedAudio = 32,
kUnsupportedAttachedText = 33,
kUnsupportedAttachedAudio = 34,
kUnsupportedDetachedText = 35,
kUnsupportedDetachedAudio = 36,
kWhatsNewAttachedText = 37,
kWhatsNewAttachedAudio = 38,
kWhatsNewDetachedText = 39,
kWhatsNewDetachedAudio = 40,
kAfterSignInAttachedText = 41,
kAfterSignInAttachedAudio = 42,
kAfterSignInDetachedText = 43,
kAfterSignInDetachedAudio = 44,
kSharedTabAttachedText = 45,
kSharedTabAttachedAudio = 46,
kSharedTabDetachedText = 47,
kSharedTabDetachedAudio = 48,
kActorTaskIconAttachedText = 49,
kActorTaskIconAttachedAudio = 50,
kActorTaskIconDetachedText = 51,
kActorTaskIconDetachedAudio = 52,
kMaxValue = kActorTaskIconDetachedAudio,
};
// LINT.ThenChange(//tools/metrics/histograms/metadata/glic/enums.xml:GlicResponseSegmentation)
// LINT.IfChange(GlicInputModesUsed)
enum class InputModesUsed {
kNone = 0,
kOnlyText = 1,
kOnlyAudio = 2,
kTextAndAudio = 3,
kMaxValue = kTextAndAudio,
};
// LINT.ThenChange(//tools/metrics/histograms/metadata/glic/enums.xml:GlicInputModesUsed)
// LINT.IfChange(AttachChangeReason)
enum class AttachChangeReason {
// Attach state changed because of a drag gesture.
kDrag = 0,
// Attach state changed because of a menu click.
kMenu = 1,
// Attachment state initialized.
kInit = 2,
kMaxValue = kInit,
};
// LINT.ThenChange(//tools/metrics/histograms/metadata/glic/enums.xml:GlicAttachChangeReason)
// Events related to requests to the Glic API from the web client.
// LINT.IfChange(GlicRequestEvent)
enum class GlicRequestEvent {
kRequestReceived = 0,
kRequestSent = 1,
kRequestHandlerException = 2,
kRequestReceivedWhileHidden = 3,
kMaxValue = kRequestReceivedWhileHidden,
};
// LINT.ThenChange(//tools/metrics/histograms/metadata/glic/enums.xml:GlicRequestEvent)
// LINT.IfChange(GlicGetContextFromFocusedTabError)
enum class GlicGetContextFromFocusedTabError {
kUnknown = 0,
kPermissionDeniedWindowNotShowing = 1,
kTabNotFound = 2,
kPermissionDeniedContextPermissionNotEnabled = 3,
kPermissionDenied = 4,
kWebContentsChanged = 5,
kPageContextNotEligible = 6,
kMaxValue = kPageContextNotEligible,
};
// LINT.ThenChange(//tools/metrics/histograms/metadata/glic/enums.xml:GlicGetContextFromFocusedTabError)
// LINT.IfChange(GlicTabPinnedForSharingResult)
enum class GlicTabPinnedForSharingResult {
kPinTabForSharingFailedTooManyTabs = 0,
kPinTabForSharingFailedNotValidForSharing = 1,
kPinTabForSharingSucceeded = 2,
kMaxValue = kPinTabForSharingSucceeded,
};
// LINT.ThenChange(//tools/metrics/histograms/metadata/glic/enums.xml:GlicTabPinnedForSharingResult)
// The different states of active tab sharing.
// LINT.IfChange(ActiveTabSharingState)
enum class ActiveTabSharingState {
kActiveTabIsShared = 0,
kCannotShareActiveTab = 1,
kNoTabCanBeShared = 2,
kTabContextPermissionNotGranted = 3,
kMaxValue = kTabContextPermissionNotGranted
};
// LINT.ThenChange(//tools/metrics/histograms/metadata/glic/enums.xml:ActiveTabSharingState)
class GlicEnabling;
class GlicSharingManager;
class GlicWindowController;
namespace internal {
class BrowserActivityObserver;
}
// Responsible for all glic web-client metrics, and all stateful glic metrics.
// Some stateless glic metrics are logged inline in the relevant code for
// convenience.
class GlicMetrics {
public:
class Delegate {
public:
virtual ~Delegate() {}
virtual gfx::Size GetWindowSize() const = 0;
virtual bool IsWindowShowing() const = 0;
virtual bool IsWindowAttached() const = 0;
virtual content::WebContents* GetContents() = 0;
virtual ActiveTabSharingState GetActiveTabSharingState() = 0;
virtual int32_t GetNumPinnedTabs() const = 0;
};
GlicMetrics(Profile* profile, GlicEnabling* enabling);
GlicMetrics(const GlicMetrics&) = delete;
GlicMetrics& operator=(const GlicMetrics&) = delete;
~GlicMetrics();
// See glic.mojom for details. These are events from the web client. The
// lifetime of the web client is scoped to that of the window, so if these
// methods are called then controller_ is guaranteed to exist.
void OnUserInputSubmitted(mojom::WebClientMode mode);
void OnResponseStarted();
void OnResponseStopped(mojom::ResponseStopCause cause);
void OnSessionTerminated();
void OnResponseRated(bool positive);
void OnTurnCompleted(mojom::WebClientModel model, base::TimeDelta duration);
void OnModelChanged(mojom::WebClientModel model);
void OnAttachedToBrowser(AttachChangeReason reason);
void OnDetachedFromBrowser(AttachChangeReason reason);
// ----Public API called by other glic classes-----
// Called when the user clicks Accept in the FRE.
void OnFreAccepted();
// Called when the glic window starts to open.
void OnGlicWindowOpen(bool attached, mojom::InvocationSource source);
// Called just after the the glic window has been loaded into the UI.
void OnGlicWindowShown(Browser* browser,
std::optional<display::Display> glic_display,
const gfx::Rect& glic_bounds);
// Called when the glic window has been opened and is ready.
void OnGlicWindowOpenAndReady();
// Called when the glic window is resized.
void OnGlicWindowResize();
// Called when the glic window starts being resized by the user.
void OnWidgetUserResizeStarted();
// Called when the glic window stops being resized by the user.
void OnWidgetUserResizeEnded();
// Called when the glic window finishes closing.
void OnGlicWindowClose(Browser* last_active_browser,
std::optional<display::Display> display,
const gfx::Rect& glic_bounds);
// Called when glic requests a scroll.
void OnGlicScrollAttempt();
// Called when scrolling starts (after glic requests to scroll) or if
// the operation fails. `success` is true if a scroll was successfully
// triggered.
void OnGlicScrollComplete(bool success);
// Called when a tab is pinned for sharing with glic. `success` is true if the
// pinning was successful.
void OnTabPinnedForSharing(GlicTabPinnedForSharingResult result);
// Called when a response is received with closed captions showing.
void LogClosedCaptionsShown();
// Logs an error that occurred while trying to get context from the focused
// tab.
void LogGetContextFromFocusedTabError(
GlicGetContextFromFocusedTabError error);
// See `last_input_mode_` for details.
mojom::WebClientMode last_input_mode() const { return last_input_mode_; }
// Must be called immediately after constructor before any calls from
// glic.mojom.
void SetControllers(GlicWindowController* window_controller,
GlicSharingManager* sharing_manager);
void SetDelegateForTesting(std::unique_ptr<Delegate> delegate);
// Must be called when context is requested.
void DidRequestContextFromFocusedTab();
void set_show_start_time(base::TimeTicks time) { show_start_time_ = time; }
void set_starting_mode(mojom::WebClientMode mode) { starting_mode_ = mode; }
mojom::WebClientModel current_model() const { return current_model_; }
private:
// Called when `impression_timer_` fires.
void OnImpressionTimerFired();
// Called when `glic_window_size_timer_` fires.
void OnGlicWindowSizeTimerFired();
// Stores the source id at the time that context is requested.
void StoreSourceId();
// Called when kGlicCompletedFre or GlicEnabling::IsAllowed() changes.
void OnMaybeEnabledAndConsentForProfileChanged();
// Called when kGlicPinnedToTabstrip changes.
void OnPinningPrefChanged();
// Called when kGlicTabContextEnabled changes.
void OnTabContextEnabledPrefChanged();
// Resets the window timing state variables.
void ResetGlicWindowPresentationTimingState();
// Returns the area in the display a given center point is.
DisplayPosition GetDisplayPositionOfPoint(
std::optional<display::Display> display,
const gfx::Point& glic_center_point);
// Returns the area relative to the given chrome browser a given center point
// is.
ChromeRelativePosition GetChromeRelativePositionOfPoint(
Browser* browser,
const gfx::Point& glic_center_point);
// Returns the percent overlap of the given glic bounds and the given chrome
// browser.
PercentOverlap GetPercentOverlapWithBrowser(Browser* browser,
const gfx::Rect& glic_bounds);
base::TimeTicks fre_accepted_time_;
// These members are cleared in OnResponseStopped.
base::TimeTicks input_submitted_time_;
mojom::WebClientMode input_mode_;
bool did_request_context_ = false;
std::set<mojom::WebClientMode> inputs_modes_used_;
int attach_change_count_ = 0;
mojom::WebClientModel current_model_ = mojom::WebClientModel::kDefault;
// Session state. `session_start_time_` is a sentinel that is cleared in
// OnGlicWindowClose() and is used to determine whether OnGlicWindowOpen was
// called.
int session_responses_ = 0;
base::TimeTicks session_start_time_;
mojom::InvocationSource invocation_source_ =
mojom::InvocationSource::kOsButton;
// Used to record impressions of glic entry points.
base::RepeatingTimer impression_timer_;
// Used to record metrics about the glic window size.
base::RepeatingTimer glic_window_size_timer_;
// A context-free source id used when no web contents is targeted.
ukm::SourceId no_url_source_id_ = ukm::NoURLSourceId();
// The source id at the time context is requested. If context was not
// requested then this is `no_url_source_id_`.
ukm::SourceId source_id_ = ukm::NoURLSourceId();
// The owner of this class is responsible for maintaining appropriate lifetime
// for controller_.
std::unique_ptr<Delegate> delegate_;
raw_ptr<Profile> profile_;
raw_ptr<GlicEnabling> enabling_;
// Whether Glic is enabled and FRE has been completed. Tracked to trigger
// metric(s) on change.
bool is_enabled_ = false;
// Set to true in OnResponseStarted() and set to false in OnResponseStopped().
// This is a workaround and should be removed, see crbug.com/399151164.
bool response_started_ = false;
std::vector<base::CallbackListSubscription> subscriptions_;
// Cache the last value of the kGlicPinnedToTabstrip pref so that we only emit
// metrics for changes to the last value.
bool is_pinned_ = false;
PrefChangeRegistrar pref_registrar_;
// The following two variables are used together for recording metrics and are
// reset together after the metric is recorded.
// The timestamp when the glic window starts to be shown.
base::TimeTicks show_start_time_;
// Web client's operation modes.
mojom::WebClientMode starting_mode_ = mojom::WebClientMode::kUnknown;
// The following variables are used for recording scroll related metrics.
// The number of scroll attempts (tracked per session and reset when the
// session ends).
int scroll_attempt_count_ = 0;
// These two variables mirror `input_submitted_time_` and
// `input_mode_`, but are only set when `OnGlicScrollAttempt()` is
// called. They are reset in `OnGlicScrollComplete()`. They are separately
// tracked because `OnGlicScrollComplete()` could potentially be called after
// `OnResponseStopped()`, which resets `input_submitted_time_` and
// `input_mode_`.
base::TimeTicks scroll_input_submitted_time_;
mojom::WebClientMode scroll_input_mode_;
// The last input mode used by the user. This is not cleared when the response
// is finished, so it can be used to attribute events that happen after the
// response has completed to the input mode that triggered them.
mojom::WebClientMode last_input_mode_ = mojom::WebClientMode::kUnknown;
std::unique_ptr<internal::BrowserActivityObserver> browser_activity_observer_;
};
} // namespace glic
#endif // CHROME_BROWSER_GLIC_GLIC_METRICS_H_