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

#ifndef COMPONENTS_PAGE_LOAD_METRICS_BROWSER_PAGE_LOAD_METRICS_UTIL_H_
#define COMPONENTS_PAGE_LOAD_METRICS_BROWSER_PAGE_LOAD_METRICS_UTIL_H_

#include <cstdint>
#include <optional>
#include <string_view>

#include "base/byte_size.h"
#include "base/metrics/histogram_functions.h"
#include "base/time/time.h"
#include "components/page_load_metrics/browser/page_load_metrics_observer.h"
#include "components/page_load_metrics/common/page_load_metrics_util.h"
#include "components/page_load_metrics/common/page_visit_final_status.h"
#include "third_party/blink/public/common/loader/loading_behavior_flag.h"
#include "url/gurl.h"

// LINT.IfChange(page_load_histogram)
// 10 ms to 10 minutes, with 100 buckets.
#define PAGE_LOAD_HISTOGRAM(name, sample)                             \
  base::UmaHistogramCustomTimes(name, sample, base::Milliseconds(10), \
                                base::Minutes(10), 100)
// LINT.ThenChange(/chrome/android/java/src/org/chromium/chrome/browser/ntp/NewTabPage.java:page_load_histogram)

// 1 ms to 10 minutes, with 100 buckets.
// Used for metrics where we want to avoid sub-10ms values being rounded
// to zero (falling into the underflow bucket), which occurs in the
// PAGE_LOAD_HISTOGRAM.
#define PAGE_LOAD_HISTOGRAM2(name, sample)                           \
  base::UmaHistogramCustomTimes(name, sample, base::Milliseconds(1), \
                                base::Minutes(10), 100)

// 1 ms to 1 minute, with 100 buckets.
#define PAGE_LOAD_SHORT_HISTOGRAM(name, sample)                      \
  base::UmaHistogramCustomTimes(name, sample, base::Milliseconds(1), \
                                base::Minutes(1), 100)

// Up to 1 hour, with 100 buckets.
#define PAGE_LOAD_LONG_HISTOGRAM(name, sample)                        \
  base::UmaHistogramCustomTimes(name, sample, base::Milliseconds(10), \
                                base::Hours(1), 100)

// Records |bytes| to |histogram_name| in kilobytes.
#define PAGE_BYTES_HISTOGRAM(histogram_name, bytes)                \
  base::UmaHistogramCustomCounts(histogram_name, bytes.InKiB(), 1, \
                                 base::MiB(500).InKiB(), 50)

// Up to 1 minute with 50 buckets.
#define INPUT_DELAY_HISTOGRAM(name, sample)                          \
  base::UmaHistogramCustomTimes(name, sample, base::Milliseconds(1), \
                                base::Seconds(60), 50)

#define PAGE_RESOURCE_COUNT_HISTOGRAM UMA_HISTOGRAM_COUNTS_10000

namespace page_load_metrics {

class PageLoadMetricsObserverDelegate;

namespace mojom {
class PageLoadTiming;
}

// Reasons a page load can be aborted.
enum PageAbortReason {
  // Represents no abort.
  ABORT_NONE,

  // The page was reloaded, possibly by the user.
  ABORT_RELOAD,

  // The page was navigated away from, via a back or forward navigation.
  ABORT_FORWARD_BACK,

  // If the page load is replaced by a new navigation. This includes link
  // clicks, typing in the omnibox (not a reload), and form submissions.
  ABORT_NEW_NAVIGATION,

  // The page load was stopped (e.g. the user presses the stop X button).
  ABORT_STOP,

  // Page load ended due to closing the tab or browser.
  ABORT_CLOSE,

  // The page load was backgrounded, e.g. the browser was minimized or the user
  // switched tabs. Note that the same page may be foregrounded in the future,
  // so this is not a 'terminal' abort type.
  ABORT_BACKGROUND,

  // We don't know why the page load ended. This is the value we assign to a
  // terminated provisional load if the only signal we get is the load finished
  // without committing, either without error or with net::ERR_ABORTED.
  ABORT_OTHER
};

// Information related to a page load abort.
struct PageAbortInfo {
  PageAbortInfo()
      : reason(ABORT_NONE),
        user_initiated_info(UserInitiatedInfo::NotUserInitiated()) {}
  PageAbortInfo(PageAbortReason reason,
                UserInitiatedInfo user_initiated_info,
                base::TimeDelta time_to_abort)
      : reason(reason),
        user_initiated_info(user_initiated_info),
        time_to_abort(time_to_abort) {}

  // The reason / cause for the abort.
  const PageAbortReason reason;

  // The fields below are only valid if reason != ABORT_NONE.

  // Information about whether the abort was initiated by a user.
  const UserInitiatedInfo user_initiated_info;

  // The time from navigation start to the time the page load was aborted.
  const base::TimeDelta time_to_abort;
};

// UMA histogram function for logging the max cumulative shift score. Adjusts
// the layout shift score to 10000x for better bucketing at the low end in UMA.
void UmaMaxCumulativeShiftScoreHistogram10000x(
    const std::string& name,
    const page_load_metrics::NormalizedCLSData& normalized_cls_data);

// Returns true if:
// - We have timing information for the event.
// - The page load started while the page was in the foreground.
// - The event occurred prior to the page being moved to the background.
// When a page is backgrounded, some events (e.g. paint) are delayed. Since
// these data points can skew the mean, they should not be mixed with timing
// events that occurred in the foreground.
// If the event time delta and background time delta are equal, we still
// consider the event to be logged in the foreground histogram since any
// background specific handling would not yet have been applied to that event.
bool WasStartedInForegroundOptionalEventInForeground(
    const std::optional<base::TimeDelta>& event,
    const PageLoadMetricsObserverDelegate& delegate);

// Returns true if:
// - We have timing information for the event.
// - The page load was prerendered, and was later activated by a navigation that
//   started in the foreground.
// - The event occurred prior to the page being moved to the background.
bool WasActivatedInForegroundOptionalEventInForeground(
    const std::optional<base::TimeDelta>& event,
    const PageLoadMetricsObserverDelegate& delegate);

bool WasStartedInForegroundOptionalEventInForegroundAfterBackForwardCacheRestore(
    const std::optional<base::TimeDelta>& event,
    const PageLoadMetricsObserverDelegate& delegate,
    size_t index);

// Returns true if:
// - We have timing information for the event.
// - The page load started in the background.
// - Moved to the foreground prior to the event.
// - Not moved back to the background prior to the event.
bool WasStartedInBackgroundOptionalEventInForeground(
    const std::optional<base::TimeDelta>& event,
    const PageLoadMetricsObserverDelegate& delegate);

// Returns true if |delegate| started in the foreground or became foregrounded
// at some point in time.
bool WasInForeground(const PageLoadMetricsObserverDelegate& delegate);

// Returns (navigation start origined) "non prerendering background start" if
// it exists, or nullopt. Here, "non prerendering background start" is the
// minimum timing `x` satisfying:
//
// - The page is background at `x`; and
// - `x` as TimeDelta is greater than or equal to navigation start (resp.
//   activation start) if the page is not prerendered (resp. is prerendered).
//
// Note that this can be different from the return value of
// `PageLoadMetricsObserverDelegate::GetTimeToFirstBackground`.
std::optional<base::TimeDelta> GetNonPrerenderingBackgroundStartTiming(
    const PageLoadMetricsObserverDelegate& delegate);

// Returns true iff event occurred in prerendered before activation or before
// background start.
//
// Precondition: `HasInvalidActivationStart` must not hold.
// In this case, arbitrary value will be returned.
bool EventOccurredBeforeNonPrerenderingBackgroundStart(
    const PageLoadMetricsObserverDelegate& delegate,
    const base::TimeDelta& event);

// Corrects an event with navigation start origin as navigation/activation
// start origin.
//
// If the page is not prerendered, returns the event as is. If the page is
// prerendered, returns activation start origined time delta. Negative values
// are truncated as zero.
base::TimeDelta CorrectEventAsNavigationOrActivationOrigined(
    const PageLoadMetricsObserverDelegate& delegate,
    const base::TimeDelta& event);

PageAbortInfo GetPageAbortInfo(const PageLoadMetricsObserverDelegate& delegate);

// Get the duration of time that the page spent in the foreground, from
// navigation start until one of the following events:
// * the load of the main resource fails
// * the page load is stopped
// * the tab hosting the page is closed
// * the render process hosting the page goes away
// * a new navigation which later commits is initiated in the same tab
// * the tab hosting the page is backgrounded
std::optional<base::TimeDelta> GetInitialForegroundDuration(
    const PageLoadMetricsObserverDelegate& delegate,
    base::TimeTicks app_background_time);

// zstd content-coded responses.
bool IsZstdUrl(const GURL& url);

// Whether the given query string contains the given component. The query
// parameter should contain the query string of a URL (the portion following
// the question mark, excluding the question mark). The component must fully
// match a component in the query string. For example, 'foo=bar' would match
// the query string 'a=b&foo=bar&c=d' but would not match 'a=b&zzzfoo=bar&c=d'
// since, though foo=bar appears in the query string, the key specified in the
// component 'foo' does not match the full key in the query string
// 'zzzfoo'. For QueryContainsComponent, the component should of the form
// 'key=value'. For QueryContainsComponentPrefix, the component should be of
// the form 'key=' (where the value is not specified).
// Note: The heuristic used by these functions will not find a component at the
// beginning of the query string if the component starts with a delimiter
// character ('?' or '#'). For example, '?foo=bar' will match the query string
// 'a=b&?foo=bar' but not the query string '?foo=bar&a=b'.
bool QueryContainsComponent(std::string_view query, std::string_view component);
bool QueryContainsComponentPrefix(std::string_view query,
                                  std::string_view component);

// Adjusts the layout shift score for UKM.
int64_t LayoutShiftUkmValue(float shift_score);

// Adjusts the layout shift score for UMA.
int32_t LayoutShiftUmaValue(float shift_score);

// Adjusts the layout shift score to 10000x for better bucketing at the low end
// in UMA.
int32_t LayoutShiftUmaValue10000(float shift_score);

// Helper function that determines the final status for a page visit for a given
// PageLoadTiming, records it to the appropriate UKM metric and UMA histogram,
// and returns the status in case the caller needs to use it elsewhere.
// A 'Page Visit' is a user perceived visit to a page, regardless of how the
// page was navigated to. See the UserPerceivedPageVisit event in
// tools/metrics/ukm.xml.
PageVisitFinalStatus RecordPageVisitFinalStatusForTiming(
    const page_load_metrics::mojom::PageLoadTiming& timing,
    const PageLoadMetricsObserverDelegate& delegate,
    ukm::SourceId source_id);

// Returns the ID if `url` contains a URL param where the param name and value
// matches the prefix pattern configured by Finch (the default param name is
// "category" and the prefix pattern is an empty string / will match anything).
// Returns std::nullopt if the param value is not recognized.
//
// For example, if the valid param name  is "cat" and the prefix pattern is
// "pattern", this function may return an ID for (1) but not for (2):
//
// (1) http://a.com?cat=pattern1
// (2) http://b.com?cat=invalid-pattern
//
// UKM Review:
// https://docs.google.com/document/d/1TSbtp5I5Bc1pbAKrrEHfmZlAwgeh6AlgAH7GbDMNPRQ/edit?disco=AAABe5h90jQ
std::optional<uint32_t> GetCategoryIdFromUrl(const GURL& url);

// Returns true if the page is controlled by a service worker.
bool IsServiceWorkerControlled(const PageLoadMetricsObserverDelegate& delegate);

// Returns true if the page is returned by a service worker synthetic response.
//
// TODO(crbug.com/40240298): Remove this once `ControllerServiceWorkerMode` is
// updated. Conceptually `IsServiceWorkerControlled()` should return true when
// the synthetic response is used. We don't need a dedicated method.
bool IsServiceWorkerSyntheticResponseEnabled(
    const PageLoadMetricsObserverDelegate& delegate);
bool IsServiceWorkerControlledOrSyntheticResponseEnabled(
    const PageLoadMetricsObserverDelegate& delegate);

// Buckets for recording PaintTiming_LargestContentfulPaintBPP; see
// also BitsPerPixelExponential enum in //tools/metrics/histograms/enums.xml.
int64_t CalculateLCPEntropyBucket(double bpp);
}  // namespace page_load_metrics

#endif  // COMPONENTS_PAGE_LOAD_METRICS_BROWSER_PAGE_LOAD_METRICS_UTIL_H_
