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

#include "components/page_load_metrics/browser/page_load_tracker.h"

#include <algorithm>
#include <memory>
#include <ostream>
#include <string>
#include <utility>

#include "base/check_op.h"
#include "base/feature_list.h"
#include "base/memory/read_only_shared_memory_region.h"
#include "base/metrics/histogram_functions.h"
#include "base/notreached.h"
#include "base/strings/stringprintf.h"
#include "base/time/default_tick_clock.h"
#include "base/time/time.h"
#include "base/trace_event/trace_event.h"
#include "components/page_load_metrics/browser/features.h"
#include "components/page_load_metrics/browser/observers/assert_page_load_metrics_observer.h"
#include "components/page_load_metrics/browser/page_load_metrics_embedder_interface.h"
#include "components/page_load_metrics/browser/page_load_metrics_forward_observer.h"
#include "components/page_load_metrics/browser/page_load_metrics_observer.h"
#include "components/page_load_metrics/browser/page_load_metrics_util.h"
#include "components/page_load_metrics/common/page_load_timing.h"
#include "content/public/browser/navigation_details.h"
#include "content/public/browser/navigation_discard_reason.h"
#include "content/public/browser/navigation_handle.h"
#include "content/public/browser/page.h"
#include "content/public/browser/render_frame_host.h"
#include "content/public/browser/render_process_host.h"
#include "content/public/browser/web_contents.h"
#include "content/public/browser/web_contents_delegate.h"
#include "content/public/browser/web_contents_observer.h"
#include "page_load_metrics_observer_delegate.h"
#include "services/metrics/public/cpp/ukm_recorder.h"
#include "services/metrics/public/cpp/ukm_source_id.h"
#include "third_party/blink/public/mojom/input/input_event.mojom-shared.h"

namespace page_load_metrics {

namespace internal {

const char kErrorEvents[] = "PageLoad.Internal.ErrorCode";
const char kPageLoadPrerender2Event[] = "PageLoad.Internal.Prerender2.Event";

}  // namespace internal

void RecordInternalError(InternalErrorLoadEvent event) {
  base::UmaHistogramEnumeration(internal::kErrorEvents, event, ERR_LAST_ENTRY);
}

// TODO(csharrison): Add a case for client side redirects, which is what JS
// initiated window.location / window.history navigations get set to.
PageEndReason EndReasonForPageTransition(ui::PageTransition transition) {
  if (transition & ui::PAGE_TRANSITION_CLIENT_REDIRECT) {
    return END_CLIENT_REDIRECT;
  }
  // Check for forward/back navigations first since there are forward/back
  // navigations that haved PAGE_TRANSITION_RELOAD but are not user reloads
  // (pull-to-refresh or preview opt-out).
  if (transition & ui::PAGE_TRANSITION_FORWARD_BACK) {
    return END_FORWARD_BACK;
  }
  if (ui::PageTransitionCoreTypeIs(transition, ui::PAGE_TRANSITION_RELOAD)) {
    return END_RELOAD;
  }
  if (ui::PageTransitionIsNewNavigation(transition)) {
    return END_NEW_NAVIGATION;
  }
  NOTREACHED()
      << "EndReasonForPageTransition received unexpected ui::PageTransition: "
      << transition;
}

bool IsNavigationUserInitiated(content::NavigationHandle* handle) {
  // TODO(crbug.com/41257523): Browser initiated navigations should have
  // HasUserGesture() set to true. In the meantime, we consider all
  // browser-initiated navigations to be user initiated.
  //
  // TODO(crbug.com/40480474): Some browser-initiated navigations incorrectly
  // report that they are renderer-initiated. We will currently report that
  // these navigations are not user initiated, when in fact they are user
  // initiated.
  return handle->HasUserGesture() || !handle->IsRendererInitiated();
}

namespace {

bool HasMonotonicFirstPaint(const mojom::PageLoadTiming& timing) {
  return timing.monotonic_paint_timing &&
         timing.monotonic_paint_timing->first_paint;
}

bool HasMonotonicFirstContentfulPaint(const mojom::PageLoadTiming& timing) {
  return timing.monotonic_paint_timing &&
         timing.monotonic_paint_timing->first_contentful_paint;
}

void DispatchEventsAfterBackForwardCacheRestore(
    PageLoadMetricsObserverInterface* observer,
    const std::vector<mojo::StructPtr<mojom::BackForwardCacheTiming>>&
        last_timings,
    const std::vector<mojo::StructPtr<mojom::BackForwardCacheTiming>>&
        new_timings) {
  if (new_timings.size() < last_timings.size()) {
    mojo::ReportBadMessage(base::StringPrintf(
        "`new_timings.size()` (%zu) must be equal to or greater than "
        "`last_timings.size()` (%zu) but is not",
        new_timings.size(), last_timings.size()));
    return;
  }

  for (size_t i = 0; i < new_timings.size(); i++) {
    auto first_paint =
        new_timings[i]->first_paint_after_back_forward_cache_restore;
    if (!first_paint.is_zero() &&
        (i >= last_timings.size() ||
         last_timings[i]
             ->first_paint_after_back_forward_cache_restore.is_zero())) {
      observer->OnFirstPaintAfterBackForwardCacheRestoreInPage(*new_timings[i],
                                                               i);
    }

    auto request_animation_frames =
        new_timings[i]
            ->request_animation_frames_after_back_forward_cache_restore;
    if (request_animation_frames.size() == 3 &&
        (i >= last_timings.size() ||
         last_timings[i]
             ->request_animation_frames_after_back_forward_cache_restore
             .empty())) {
      observer->OnRequestAnimationFramesAfterBackForwardCacheRestoreInPage(
          *new_timings[i], i);
    }

    auto first_input_delay =
        new_timings[i]->first_input_delay_after_back_forward_cache_restore;
    if (first_input_delay.has_value() &&
        (i >= last_timings.size() ||
         !last_timings[i]
              ->first_input_delay_after_back_forward_cache_restore
              .has_value())) {
      observer->OnFirstInputAfterBackForwardCacheRestoreInPage(*new_timings[i],
                                                               i);
    }
  }
}

void DispatchObserverTimingCallbacks(PageLoadMetricsObserverInterface* observer,
                                     const mojom::PageLoadTiming& last_timing,
                                     const mojom::PageLoadTiming& new_timing) {
  if (!last_timing.Equals(new_timing)) {
    observer->OnTimingUpdate(nullptr, new_timing);
  }
  if (new_timing.document_timing->dom_content_loaded_event_start &&
      !last_timing.document_timing->dom_content_loaded_event_start) {
    observer->OnDomContentLoadedEventStart(new_timing);
  }
  if (new_timing.document_timing->load_event_start &&
      !last_timing.document_timing->load_event_start) {
    observer->OnLoadEventStart(new_timing);
  }
  if (new_timing.interactive_timing->first_input_delay &&
      !last_timing.interactive_timing->first_input_delay) {
    observer->OnFirstInputInPage(new_timing);
  }
  if (new_timing.paint_timing->first_paint &&
      !last_timing.paint_timing->first_paint) {
    observer->OnFirstPaintInPage(new_timing);
  }
  DispatchEventsAfterBackForwardCacheRestore(
      observer, last_timing.back_forward_cache_timings,
      new_timing.back_forward_cache_timings);
  if (new_timing.paint_timing->first_image_paint &&
      !last_timing.paint_timing->first_image_paint) {
    observer->OnFirstImagePaintInPage(new_timing);
  }
  if (new_timing.paint_timing->first_contentful_paint &&
      !last_timing.paint_timing->first_contentful_paint) {
    observer->OnFirstContentfulPaintInPage(new_timing);
  }
  if (HasMonotonicFirstPaint(new_timing) &&
      !HasMonotonicFirstPaint(last_timing)) {
    observer->OnMonotonicFirstPaintInPage(new_timing);
  }
  if (new_timing.paint_timing->first_meaningful_paint &&
      !last_timing.paint_timing->first_meaningful_paint) {
    observer->OnFirstMeaningfulPaintInMainFrameDocument(new_timing);
  }
  if (new_timing.parse_timing->parse_start &&
      !last_timing.parse_timing->parse_start) {
    observer->OnParseStart(new_timing);
  }
  if (new_timing.parse_timing->parse_stop &&
      !last_timing.parse_timing->parse_stop) {
    observer->OnParseStop(new_timing);
  }
  if (HasMonotonicFirstContentfulPaint(new_timing) &&
      !HasMonotonicFirstContentfulPaint(last_timing)) {
    observer->OnMonotonicFirstContentfulPaintInPage(new_timing);
  }
  if (new_timing.domain_lookup_timing->domain_lookup_start &&
      !last_timing.domain_lookup_timing->domain_lookup_start) {
    observer->OnDomainLookupStart(new_timing);
  }
  if (new_timing.domain_lookup_timing->domain_lookup_end &&
      !last_timing.domain_lookup_timing->domain_lookup_end) {
    observer->OnDomainLookupEnd(new_timing);
  }
  if (new_timing.connect_start && !last_timing.connect_start) {
    observer->OnConnectStart(new_timing);
  }
  if (new_timing.connect_end && !last_timing.connect_end) {
    observer->OnConnectEnd(new_timing);
  }
  if (new_timing.user_timing_mark_fully_loaded !=
      last_timing.user_timing_mark_fully_loaded) {
    observer->OnUserTimingMarkFullyLoaded(new_timing);
  }
  if (new_timing.user_timing_mark_fully_visible !=
      last_timing.user_timing_mark_fully_visible) {
    observer->OnUserTimingMarkFullyVisible(new_timing);
  }
  if (new_timing.user_timing_mark_interactive !=
      last_timing.user_timing_mark_interactive) {
    observer->OnUserTimingMarkInteractive(new_timing);
  }
}

internal::PageLoadTrackerPageType CalculatePageType(
    content::NavigationHandle* navigation_handle) {
  if (navigation_handle->IsInPrerenderedMainFrame()) {
    return internal::PageLoadTrackerPageType::kPrerenderPage;
  } else if (navigation_handle->GetNavigatingFrameType() ==
             content::FrameType::kFencedFrameRoot) {
    return internal::PageLoadTrackerPageType::kFencedFramesPage;
  }
  return internal::PageLoadTrackerPageType::kPrimaryPage;
}

bool CalculateIsOriginVisit(bool is_first_navigation,
                            ui::PageTransition transition) {
  if (is_first_navigation) {
    return true;
  }
  if (ui::PageTransitionCoreTypeIs(transition, ui::PAGE_TRANSITION_LINK) ||
      ui::PageTransitionCoreTypeIs(transition, ui::PAGE_TRANSITION_RELOAD)) {
    return false;
  }
  return true;
}

void RegisterObservers(PageLoadTracker* tracker,
                       PageLoadMetricsEmbedderInterface* embedder,
                       content::NavigationHandle* navigation_handle) {
#if DCHECK_IS_ON()
  // Link Preview doesn't emit activation event yet and assertion of event
  // orders fail.
  //
  // TODO(b:302999778): Reenable it.
  tracker->AddObserver(std::make_unique<AssertPageLoadMetricsObserver>());
#endif
  embedder->RegisterObservers(tracker, navigation_handle);
}

}  // namespace

PageLoadTracker::PageLoadTracker(
    InForegroundBool in_foreground,
    PageLoadMetricsEmbedderInterface* embedder_interface,
    const GURL& currently_committed_url,
    IsFirstNavigationInWebContentsBool is_first_navigation_in_web_contents,
    IsReloadAfterDiscardBool is_reload_after_discard,
    content::NavigationHandle* navigation_handle,
    UserInitiatedInfo user_initiated_info,
    ukm::SourceId source_id,
    base::WeakPtr<PageLoadTracker> parent_tracker)
    : did_stop_tracking_(false),
      navigation_id_(navigation_handle->GetNavigationId()),
      navigation_start_(navigation_handle->NavigationStart()),
      url_(navigation_handle->GetURL()),
      start_url_(navigation_handle->GetURL()),
      visibility_tracker_(base::DefaultTickClock::GetInstance(),
                          *in_foreground),
      did_commit_(false),
      page_end_reason_(END_NONE),
      page_end_user_initiated_info_(UserInitiatedInfo::NotUserInitiated()),
      started_in_foreground_(in_foreground),
      last_dispatched_merged_page_timing_(CreatePageLoadTiming()),
      user_initiated_info_(user_initiated_info),
      embedder_interface_(embedder_interface),
      metrics_update_dispatcher_(this, navigation_handle, embedder_interface),
      source_id_(source_id),
      web_contents_(navigation_handle->GetWebContents()),
      is_first_navigation_in_web_contents_(is_first_navigation_in_web_contents),
      is_reload_after_discard_(is_reload_after_discard),
      is_origin_visit_(
          CalculateIsOriginVisit(*is_first_navigation_in_web_contents,
                                 navigation_handle->GetPageTransition())),
      navigation_scenario_(
          embedder_interface->GetNavigationScenario(navigation_handle)),
      page_type_(CalculatePageType(navigation_handle)),
      parent_tracker_(std::move(parent_tracker)) {
  DCHECK(!navigation_handle->HasCommitted());
  RegisterObservers(this, embedder_interface, navigation_handle);
  switch (page_type_) {
    case internal::PageLoadTrackerPageType::kPrimaryPage:
      CHECK_NE(ukm::kInvalidSourceId, source_id_);
      InvokeAndPruneObservers(
          "PageLoadMetricsObserver::OnStart",
          base::BindRepeating(
              [](content::NavigationHandle* navigation_handle,
                 const GURL& currently_committed_url,
                 bool started_in_foreground,
                 PageLoadMetricsObserverInterface* observer) {
                return observer->OnStart(navigation_handle,
                                         currently_committed_url,
                                         started_in_foreground);
              },
              navigation_handle, currently_committed_url,
              started_in_foreground_),
          /*permit_forwarding=*/false);
      break;
    case internal::PageLoadTrackerPageType::kPrerenderPage:
      CHECK(!started_in_foreground_);
      CHECK_EQ(ukm::kInvalidSourceId, source_id_);
      prerendering_state_ = PrerenderingState::kInPrerendering;
      InvokeAndPruneObservers(
          "PageLoadMetricsObserver::OnPrerenderStart",
          base::BindRepeating(
              [](content::NavigationHandle* navigation_handle,
                 const GURL& currently_committed_url,
                 PageLoadMetricsObserverInterface* observer) {
                return observer->OnPrerenderStart(navigation_handle,
                                                  currently_committed_url);
              },
              navigation_handle, currently_committed_url),
          /*permit_forwarding=*/false);
      base::UmaHistogramEnumeration(
          internal::kPageLoadPrerender2Event,
          internal::PageLoadPrerenderEvent::kNavigationInPrerenderedMainFrame);
      break;
    case internal::PageLoadTrackerPageType::kFencedFramesPage:
      CHECK_NE(ukm::kInvalidSourceId, source_id_);
      InvokeAndPruneObservers(
          "PageLoadMetricsObserver::OnFencedFramesStart",
          base::BindRepeating(
              [](content::NavigationHandle* navigation_handle,
                 const GURL& currently_committed_url,
                 PageLoadMetricsObserverInterface* observer) {
                return observer->OnFencedFramesStart(navigation_handle,
                                                     currently_committed_url);
              },
              navigation_handle, currently_committed_url),
          /*permit_forwarding=*/true);
      break;
  }
}

PageLoadTracker::~PageLoadTracker() {
  if (did_stop_tracking_) {
    return;
  }

  metrics_update_dispatcher_.ShutDown();

  if (page_end_time_.is_null()) {
    // page_end_time_ can be unset in some cases, such as when a navigation is
    // aborted by a navigation that started before it. In these cases, set the
    // end time to the current time.
    RecordInternalError(ERR_NO_PAGE_LOAD_END_TIME);
    NotifyPageEnd(END_OTHER, UserInitiatedInfo::NotUserInitiated(),
                  base::TimeTicks::Now(), true);
  }

  if (!did_commit_) {
    if (!failed_provisional_load_info_) {
      RecordInternalError(ERR_NO_COMMIT_OR_FAILED_PROVISIONAL_LOAD);
    }
  } else if (page_load_metrics::IsEmpty(metrics_update_dispatcher_.timing())) {
    RecordInternalError(ERR_NO_IPCS_RECEIVED);
  }

  for (const auto& observer : observers_) {
    if (failed_provisional_load_info_) {
      observer->OnFailedProvisionalLoad(*failed_provisional_load_info_);
    } else {
      DCHECK(did_commit_);
      observer->OnComplete(metrics_update_dispatcher_.timing());
    }
  }
}

void PageLoadTracker::PageHidden() {
  // Only log the first time we background in a given page load.
  if (!first_background_time_.has_value() ||
      (!back_forward_cache_restores_.empty() &&
       !back_forward_cache_restores_.back()
            .first_background_time.has_value())) {
    // The possible visibility state transitions and events are:
    //
    // A. navigation_start (fg) -> first background (bg)
    //    -> first foreground (fg)
    // B. navigation_start (bg) -> first foreground (fg)
    //    -> first background (bg)
    // C. navigation_start (bg) -> activation_start (fg)
    //    -> first background (bg) -> first foreground (fg)
    //
    // where fg = foreground, bg = background. A and B are non prerendered and C
    // is prerendered.
    //
    // PageShown and PgaeHidden are not called for navigation_start and
    // actiivation_start; called when the visibility is changed after
    // navigation_start (resp. activation_start) for non prerendered (resp.
    // prerendered) pages.
    //
    // Here we check that the first background follows some event in foreground.
    if (!first_background_time_.has_value()) {
      if (prerendering_state_ == PrerenderingState::kNoPrerendering) {
        DCHECK_EQ(!started_in_foreground_, first_foreground_time_.has_value());
      } else {
        DCHECK(!first_foreground_time_.has_value());
      }
    }

    base::TimeTicks background_time = base::TimeTicks::Now();
    ClampBrowserTimestampIfInterProcessTimeTickSkew(&background_time);
    DCHECK_GE(background_time, navigation_start_);

    if (!first_background_time_.has_value()) {
      first_background_time_ = background_time;
    }

    if (!back_forward_cache_restores_.empty() &&
        !back_forward_cache_restores_.back()
             .first_background_time.has_value()) {
      back_forward_cache_restores_.back().first_background_time =
          background_time -
          back_forward_cache_restores_.back().navigation_start_time;
    }
  }
  visibility_tracker_.OnHidden();
  InvokeAndPruneObservers("PageLoadMetricsObserver::OnHidden",
                          base::BindRepeating(
                              [](const mojom::PageLoadTiming* timing,
                                 PageLoadMetricsObserverInterface* observer) {
                                return observer->OnHidden(*timing);
                              },
                              &metrics_update_dispatcher_.timing()),
                          /*permit_forwarding=*/false);
}

void PageLoadTracker::PageShown() {
  // Only log the first time we foreground in a given page load.
  if (!first_foreground_time_.has_value()) {
    // See comment about visibility state transitions in PageHidden.
    //
    // Here we check that the first foreground follows some event in background.
    if (prerendering_state_ == PrerenderingState::kNoPrerendering) {
      DCHECK_EQ(started_in_foreground_, first_background_time_.has_value());
    } else {
      // When a prerendered page is activated in a background tab (e.g.
      // ctrl+click), PageHidden() is never called because there was no
      // visible-to-hidden transition. In that case first_background_time_
      // won't be set. Metrics observers handle this via
      // GetNonPrerenderingBackgroundStartTiming() which uses activation_start
      // for background-activated prerenders.
      DCHECK(first_background_time_.has_value() ||
             visibility_at_activation_ == PageVisibility::kBackground);
    }

    base::TimeTicks foreground_time = base::TimeTicks::Now();
    ClampBrowserTimestampIfInterProcessTimeTickSkew(&foreground_time);
    DCHECK_GE(foreground_time, navigation_start_);
    first_foreground_time_ = foreground_time;
  }

  visibility_tracker_.OnShown();
  InvokeAndPruneObservers(
      "PageLoadMetricsObserver::OnShown",
      base::BindRepeating([](PageLoadMetricsObserverInterface* observer) {
        return observer->OnShown();
      }),
      /*permit_forwarding=*/false);
}

void PageLoadTracker::RenderFrameDeleted(content::RenderFrameHost* rfh) {
  if (parent_tracker_) {
    // Notify the parent of a deletion of RenderFrameHost of a subframe.
    parent_tracker_->RenderFrameDeleted(rfh);
  }

  for (const auto& observer : observers_) {
    observer->OnRenderFrameDeleted(rfh);
  }
}

void PageLoadTracker::FrameTreeNodeDeleted(
    content::FrameTreeNodeId frame_tree_node_id) {
  if (parent_tracker_) {
    // Notify the parent of a deletion of FrameTreeNode of a subframe.
    //
    // Note that deletion of main frames are forwarded by
    // MetrcisWebContentsObserver.
    parent_tracker_->FrameTreeNodeDeleted(frame_tree_node_id);
  }

  metrics_update_dispatcher_.OnSubFrameDeleted(frame_tree_node_id);
  largest_contentful_paint_handler_.OnSubFrameDeleted(frame_tree_node_id);
  for (const auto& observer : observers_) {
    observer->OnSubFrameDeleted(frame_tree_node_id);
  }
}

void PageLoadTracker::WillProcessNavigationResponse(
    content::NavigationHandle* navigation_handle) {
  DCHECK(!navigation_request_id_.has_value());
  navigation_request_id_ = navigation_handle->GetGlobalRequestID();
}

void PageLoadTracker::Commit(content::NavigationHandle* navigation_handle) {
  // We don't deliver OnCommit() for activation. Prerendered pages will see
  // DidActivatePrerenderedPage() instead.
  // Event records below are also not needed as we did them for the initial
  // navigation on starting prerendering.
  DCHECK(!navigation_handle->IsPrerenderedPageActivation());

  if (parent_tracker_) {
    // Notify the parent of the inner main frame navigation as a sub-frame
    // navigation.
    parent_tracker_->DidFinishSubFrameNavigation(navigation_handle);
  } else if (navigation_handle->IsPrerenderedPageActivation()) {
    // We don't deliver OnCommit() for activation. Prerendered pages will see
    // DidActivatePrerenderedPage() instead.
    // Event records below are also not needed as we did them for the initial
    // navigation on starting prerendering.
    NOTREACHED();
  }

  did_commit_ = true;
  url_ = navigation_handle->GetURL();
  // Some transitions (like CLIENT_REDIRECT) are only known at commit time.
  user_initiated_info_.user_gesture = navigation_handle->HasUserGesture();

  if (navigation_handle->IsInMainFrame()) {
    largest_contentful_paint_handler_.RecordMainFrameTreeNodeId(
        navigation_handle->GetFrameTreeNodeId());
    experimental_largest_contentful_paint_handler_.RecordMainFrameTreeNodeId(
        navigation_handle->GetFrameTreeNodeId());
  }
  InvokeAndPruneObservers(
      "PageLoadMetricsObserver::ShouldObserveMimeType",
      base::BindRepeating(
          [](const std::string& mime_type,
             PageLoadMetricsObserverInterface* observer) {
            return observer->ShouldObserveMimeType(mime_type);
          },
          // Query with the outermost page's MIME type so that we can ask each
          // observer with information for the page they are interested in.
          navigation_handle->GetRenderFrameHost()
              ->GetOutermostMainFrameOrEmbedder()
              ->GetPage()
              .GetContentsMimeType()),
      /*permit_forwarding=*/false);
  InvokeAndPruneObservers(
      "PageLoadMetricsObserver::ShouldObserveScheme",
      base::BindRepeating(
          [](const GURL& url, PageLoadMetricsObserverInterface* observer) {
            return observer->ShouldObserveScheme(url);
          },
          navigation_handle->GetURL()),
      /*permit_forwarding=*/false);
  InvokeAndPruneObservers("PageLoadMetricsObserver::OnCommit",
                          base::BindRepeating(
                              [](content::NavigationHandle* navigation_handle,
                                 PageLoadMetricsObserverInterface* observer) {
                                return observer->OnCommit(navigation_handle);
                              },
                              navigation_handle),
                          /*permit_forwarding=*/false);
}

void PageLoadTracker::DidActivatePrerenderedPage(
    content::NavigationHandle* navigation_handle) {
  DCHECK_EQ(prerendering_state_, PrerenderingState::kInPrerendering);

  prerendering_state_ = PrerenderingState::kActivatedNoActivationStart;
  source_id_ = ukm::ConvertToSourceId(navigation_handle->GetNavigationId(),
                                      ukm::SourceIdType::NAVIGATION_ID);

  switch (GetWebContents()->GetVisibility()) {
    case content::Visibility::HIDDEN:
    case content::Visibility::OCCLUDED:
      visibility_at_activation_ = PageVisibility::kBackground;
      break;
    case content::Visibility::VISIBLE:
      visibility_at_activation_ = PageVisibility::kForeground;
      break;
  }

  for (const auto& observer : observers_) {
    observer->DidActivatePrerenderedPage(navigation_handle);
  }

  base::UmaHistogramEnumeration(
      internal::kPageLoadPrerender2Event,
      internal::PageLoadPrerenderEvent::kPrerenderActivationNavigation);
}

void PageLoadTracker::DidCommitSameDocumentNavigation(
    content::NavigationHandle* navigation_handle) {
  if (parent_tracker_) {
    // Notify the parent of the inner main frame navigation as a sub-frame
    // navigation.
    parent_tracker_->DidFinishSubFrameNavigation(navigation_handle);
  }

  // For main frame same document navigations, maintain a mapping to their UKM
  // Source ID. This allows us to later record soft navigations with their
  // correct URL. We use the same document metrics token for this mapping
  // because we may only learn of the soft navigation when |navigation_handle|
  // is already destroyed, yet we do need to record toward that UKM Source ID to
  // get the URL correct.
  if (navigation_handle->IsInMainFrame()) {
    std::optional<base::UnguessableToken> token =
        navigation_handle->GetSameDocumentMetricsToken();
    CHECK(token);
    CHECK(!token->is_empty());
    source_id_by_same_document_metrics_token_.try_emplace(
        *token, ukm::ConvertToSourceId(navigation_handle->GetNavigationId(),
                                       ukm::SourceIdObj::Type::NAVIGATION_ID));
  }

  for (const auto& observer : observers_) {
    observer->OnCommitSameDocumentNavigation(navigation_handle);
  }
}

void PageLoadTracker::DidInternalNavigationAbort(
    content::NavigationHandle* navigation_handle) {
  if (parent_tracker_) {
    // Notify the parent of the inner main frame navigation as a sub-frame
    // navigation.
    parent_tracker_->DidFinishSubFrameNavigation(navigation_handle);
  }

  for (const auto& observer : observers_) {
    observer->OnDidInternalNavigationAbort(navigation_handle);
  }
}

void PageLoadTracker::ReadyToCommitNavigation(
    content::NavigationHandle* navigation_handle) {
  // Don't notify the parent as inner main frame's events are converted to
  // sub-frames events for the parent, but this event is only for the main
  // frame.

  for (const auto& observer : observers_) {
    observer->ReadyToCommitNextNavigation(navigation_handle);
  }
}

void PageLoadTracker::DidFinishSubFrameNavigation(
    content::NavigationHandle* navigation_handle) {
  if (parent_tracker_) {
    // Notify the parent of inner frame navigations.
    parent_tracker_->DidFinishSubFrameNavigation(navigation_handle);
  }
  metrics_update_dispatcher_.DidFinishSubFrameNavigation(navigation_handle);
  largest_contentful_paint_handler_.OnDidFinishSubFrameNavigation(
      navigation_handle, navigation_start_);
  experimental_largest_contentful_paint_handler_.OnDidFinishSubFrameNavigation(
      navigation_handle, navigation_start_);
  for (const auto& observer : observers_) {
    observer->OnDidFinishSubFrameNavigation(navigation_handle);
  }
}

void PageLoadTracker::FailedProvisionalLoad(
    content::NavigationHandle* navigation_handle,
    base::TimeTicks failed_load_time) {
  DCHECK(!failed_provisional_load_info_);
  if (parent_tracker_) {
    // Notify the parent of the inner main frame navigation as a sub-frame
    // navigation.
    parent_tracker_->DidFinishSubFrameNavigation(navigation_handle);
  }
  CHECK(navigation_handle->GetNavigationDiscardReason().has_value());
  failed_provisional_load_info_ = std::make_unique<FailedProvisionalLoadInfo>(
      failed_load_time - navigation_handle->NavigationStart(),
      navigation_handle->GetNetErrorCode(),
      navigation_handle->GetNetExtendedErrorCode(),
      navigation_handle->GetErrorNavigationTrigger(),
      navigation_handle->GetNavigationDiscardReason().value());
}

void PageLoadTracker::DidUpdateNavigationHandleTiming(
    content::NavigationHandle* navigation_handle) {
  InvokeAndPruneObservers(
      "PageLoadMetricsObserver::OnNavigationHandleTimingUpdated",
      base::BindRepeating(
          [](content::NavigationHandle* navigation_handle,
             PageLoadMetricsObserverInterface* observer) {
            return observer->OnNavigationHandleTimingUpdated(navigation_handle);
          },
          navigation_handle),
      /*permit_forwarding=*/false);
}

void PageLoadTracker::Redirect(content::NavigationHandle* navigation_handle) {
  url_ = navigation_handle->GetURL();
  InvokeAndPruneObservers("PageLoadMetricsObserver::Redirect",
                          base::BindRepeating(
                              [](content::NavigationHandle* navigation_handle,
                                 PageLoadMetricsObserverInterface* observer) {
                                return observer->OnRedirect(navigation_handle);
                              },
                              navigation_handle),
                          /*permit_forwarding=*/false);
}

void PageLoadTracker::OnInputEvent(const blink::WebInputEvent& event) {
  static const bool do_not_send_continuous_events =
      !base::FeatureList::IsEnabled(
          features::kSendContinuousInputEventsToObservers);
  using Type = blink::mojom::EventType;
  const bool is_continuous_event =
      (event.GetType() == Type::kTouchMove ||
       event.GetType() == Type::kGestureScrollUpdate ||
       event.GetType() == Type::kGesturePinchUpdate);
  if (do_not_send_continuous_events && is_continuous_event) {
    return;
  }

  // TODO(b/328601354): Confirm continuous input events are not required for
  // page load tracker observers and rename the API to reflect the same.
  for (const auto& observer : observers_) {
    observer->OnUserInput(event, metrics_update_dispatcher_.timing());
  }
}

void PageLoadTracker::FlushMetricsOnAppEnterBackground() {
  metrics_update_dispatcher()->FlushPendingTimingUpdates();
  metrics_update_dispatcher()->FlushSoftNavigationMetrics();

  InvokeAndPruneObservers(
      "PageLoadMetricsObserver::FlushMetricsOnAppEnterBackground",
      base::BindRepeating(
          [](const mojom::PageLoadTiming* timing,
             PageLoadMetricsObserverInterface* observer) {
            return observer->FlushMetricsOnAppEnterBackground(*timing);
          },
          &metrics_update_dispatcher_.timing()),
      /*permit_forwarding=*/false);
}

void PageLoadTracker::OnLoadedResource(
    const ExtraRequestCompleteInfo& extra_request_complete_info) {
  for (const auto& observer : observers_) {
    observer->OnLoadedResource(extra_request_complete_info);
  }
}

void PageLoadTracker::DidLoadResourceFromMemoryCache(
    const MemoryResourceLoadInfo& memory_resource_load_info) {
  for (const auto& observer : observers_) {
    observer->DidLoadResourceFromMemoryCache(memory_resource_load_info);
  }
}

void PageLoadTracker::FrameReceivedUserActivation(
    content::RenderFrameHost* rfh) {
  for (const auto& observer : observers_) {
    observer->FrameReceivedUserActivation(rfh);
  }
}

void PageLoadTracker::FrameDisplayStateChanged(
    content::RenderFrameHost* render_frame_host,
    bool is_display_none) {
  for (const auto& observer : observers_) {
    observer->FrameDisplayStateChanged(render_frame_host, is_display_none);
  }
}

void PageLoadTracker::FrameSizeChanged(
    content::RenderFrameHost* render_frame_host,
    const gfx::Size& frame_size) {
  for (const auto& observer : observers_) {
    observer->FrameSizeChanged(render_frame_host, frame_size);
  }
}

void PageLoadTracker::OnCookiesRead(
    const GURL& url,
    const GURL& first_party_url,
    bool blocked_by_policy,
    bool is_ad_tagged,
    const net::CookieSettingOverrides& cookie_setting_overrides,
    bool is_partitioned_access) {
  for (const auto& observer : observers_) {
    observer->OnCookiesRead(url, first_party_url, blocked_by_policy,
                            is_ad_tagged, cookie_setting_overrides,
                            is_partitioned_access);
  }
}

void PageLoadTracker::OnCookieChange(
    const GURL& url,
    const GURL& first_party_url,
    const net::CanonicalCookie& cookie,
    bool blocked_by_policy,
    bool is_ad_tagged,
    const net::CookieSettingOverrides& cookie_setting_overrides,
    bool is_partitioned_access) {
  for (const auto& observer : observers_) {
    observer->OnCookieChange(url, first_party_url, cookie, blocked_by_policy,
                             is_ad_tagged, cookie_setting_overrides,
                             is_partitioned_access);
  }
}

void PageLoadTracker::OnStorageAccessed(const GURL& url,
                                        const GURL& first_party_url,
                                        bool blocked_by_policy,
                                        StorageType access_type) {
  for (const auto& observer : observers_) {
    observer->OnStorageAccessed(url, first_party_url, blocked_by_policy,
                                access_type);
  }
}

void PageLoadTracker::StopTracking() {
  did_stop_tracking_ = true;
  observers_map_.clear();
  observers_.clear();
}

void PageLoadTracker::AddObserver(
    std::unique_ptr<PageLoadMetricsObserverInterface> observer) {
  observer->SetDelegate(this);

  if (observer->GetObserverName()) {
    DCHECK(observers_map_.find(observer->GetObserverName()) ==
           observers_map_.end())
        << "We expect that observer's class and name is unique in trackers. "
           "Note that observer's class can be non-unique in test, e.g. "
           "PageLoadMetricsTestWaiter. In that case, use a unique name in "
           "the test. See also constructor of PageLoadMetricsTestWaiter.";
    observers_map_.emplace(observer->GetObserverName(), observer.get());
  }

  observers_.push_back(std::move(observer));
}

base::WeakPtr<PageLoadMetricsObserverInterface> PageLoadTracker::FindObserver(
    const char* name) {
  auto it = observers_map_.find(name);
  if (it != observers_map_.end()) {
    return it->second->GetWeakPtr();
  }
  return nullptr;
}

void PageLoadTracker::ClampBrowserTimestampIfInterProcessTimeTickSkew(
    base::TimeTicks* event_time) {
  DCHECK(event_time != nullptr);
  // Windows 10 GCE bot non-deterministically failed because TimeTicks::Now()
  // called in the browser process e.g. commit_time was less than
  // navigation_start_ that was populated in the renderer process because the
  // clock was not system-wide monotonic.
  // Note that navigation_start_ can also be set in the browser process in
  // some cases and in those cases event_time should never be <
  // navigation_start_. If it is due to a code error and it gets clamped in this
  // function, on high resolution systems it should lead to a dcheck failure.

  // TODO(shivanisha): Currently IsHighResolution is the best way to check
  // if the clock is system-wide monotonic. However IsHighResolution
  // does a broader check to see if the clock in use is high resolution
  // which also implies it is system-wide monotonic (on Windows).
  if (base::TimeTicks::IsHighResolution()) {
    DCHECK(event_time->is_null() || *event_time >= navigation_start_);
    return;
  }

  if (!event_time->is_null() && *event_time < navigation_start_) {
    RecordInternalError(ERR_INTER_PROCESS_TIME_TICK_SKEW);
    *event_time = navigation_start_;
  }
}

bool PageLoadTracker::HasMatchingNavigationRequestID(
    const content::GlobalRequestID& request_id) const {
  DCHECK(request_id != content::GlobalRequestID());
  return navigation_request_id_.has_value() &&
         navigation_request_id_.value() == request_id;
}

void PageLoadTracker::NotifyPageEnd(PageEndReason page_end_reason,
                                    UserInitiatedInfo user_initiated_info,
                                    base::TimeTicks timestamp,
                                    bool is_certainly_browser_timestamp) {
  DCHECK_NE(page_end_reason, END_NONE);
  // Use UpdatePageEnd to update an already notified PageLoadTracker.
  if (page_end_reason_ != END_NONE) {
    return;
  }

  UpdatePageEndInternal(page_end_reason, user_initiated_info, timestamp,
                        is_certainly_browser_timestamp);
}

void PageLoadTracker::UpdatePageEnd(PageEndReason page_end_reason,
                                    UserInitiatedInfo user_initiated_info,
                                    base::TimeTicks timestamp,
                                    bool is_certainly_browser_timestamp) {
  DCHECK_NE(page_end_reason, END_NONE);
  DCHECK_NE(page_end_reason, END_OTHER);
  DCHECK_EQ(page_end_reason_, END_OTHER);
  DCHECK(!page_end_time_.is_null());
  if (page_end_time_.is_null() || page_end_reason_ != END_OTHER) {
    return;
  }

  // For some aborts (e.g. navigations), the initiated timestamp can be earlier
  // than the timestamp that aborted the load. Taking the minimum gives the
  // closest user initiated time known.
  UpdatePageEndInternal(page_end_reason, user_initiated_info,
                        std::min(page_end_time_, timestamp),
                        is_certainly_browser_timestamp);
}

bool PageLoadTracker::IsLikelyProvisionalAbort(
    base::TimeTicks abort_cause_time) const {
  // Note that |abort_cause_time - page_end_time_| can be negative.
  return page_end_reason_ == END_OTHER &&
         (abort_cause_time - page_end_time_).InMilliseconds() < 100;
}

void PageLoadTracker::UpdatePageEndInternal(
    PageEndReason page_end_reason,
    UserInitiatedInfo user_initiated_info,
    base::TimeTicks timestamp,
    bool is_certainly_browser_timestamp) {
  // When a provisional navigation commits, that navigation's start time is
  // interpreted as the abort time for other provisional loads in the tab.
  // However, this only makes sense if the committed load started after the
  // aborted provisional loads started. Thus we ignore cases where the committed
  // load started before the aborted provisional load, as this would result in
  // recording a negative time-to-abort. The real issue here is that we have to
  // infer the cause of aborts. It would be better if the navigation code could
  // instead report the actual cause of an aborted navigation. See crbug/571647
  // for details.
  if (timestamp < navigation_start_) {
    RecordInternalError(ERR_END_BEFORE_NAVIGATION_START);
    page_end_reason_ = END_NONE;
    page_end_time_ = base::TimeTicks();
    return;
  }
  page_end_reason_ = page_end_reason;
  page_end_time_ = timestamp;
  // A client redirect can never be user initiated. Due to the way Blink
  // implements user gesture tracking, where all events that occur within a few
  // seconds after a user interaction are considered to be triggered by user
  // activation (based on the HTML spec:
  // https://html.spec.whatwg.org/multipage/interaction.html#tracking-user-activation),
  // these navs may sometimes be reported as user initiated by Blink. Thus, we
  // explicitly filter these types of aborts out when deciding if the abort was
  // user initiated.
  if (page_end_reason != END_CLIENT_REDIRECT) {
    page_end_user_initiated_info_ = user_initiated_info;
  }

  if (is_certainly_browser_timestamp) {
    ClampBrowserTimestampIfInterProcessTimeTickSkew(&page_end_time_);
  }
  if (page_end_reason_ == END_RENDER_PROCESS_GONE) {
    for (const auto& observer : observers_) {
      observer->OnPrimaryPageRenderProcessGone();
    }
  }
}

void PageLoadTracker::MediaStartedPlaying(
    const content::WebContentsObserver::MediaPlayerInfo& video_type,
    content::RenderFrameHost* render_frame_host) {
  for (const auto& observer : observers_) {
    observer->MediaStartedPlaying(video_type, render_frame_host);
  }
}

bool PageLoadTracker::IsPageMainFrame(content::RenderFrameHost* rfh) const {
  DCHECK(page_main_frame_id_);
  return rfh->GetGlobalId() == page_main_frame_id_;
}

void PageLoadTracker::OnTimingChanged() {
  DCHECK(!last_dispatched_merged_page_timing_->Equals(
      metrics_update_dispatcher_.timing()));

  const mojom::PageLoadTiming& new_timing = metrics_update_dispatcher_.timing();

  if (new_timing.activation_start &&
      !last_dispatched_merged_page_timing_->activation_start &&
      prerendering_state_ != PrerenderingState::kNoPrerendering) {
    CHECK_EQ(prerendering_state_,
             PrerenderingState::kActivatedNoActivationStart);
    prerendering_state_ = PrerenderingState::kActivated;
    activation_start_ = new_timing.activation_start;
  }

  const mojom::PaintTimingPtr& paint_timing =
      metrics_update_dispatcher_.timing().paint_timing;

  largest_contentful_paint_handler_.RecordMainFrameTiming(
      *paint_timing->largest_contentful_paint,
      paint_timing->first_input_or_scroll_notified_timestamp);
  experimental_largest_contentful_paint_handler_.RecordMainFrameTiming(
      *paint_timing->experimental_largest_contentful_paint,
      paint_timing->first_input_or_scroll_notified_timestamp);

  for (const auto& observer : observers_) {
    DispatchObserverTimingCallbacks(
        observer.get(), *last_dispatched_merged_page_timing_, new_timing);
  }
  last_dispatched_merged_page_timing_ =
      metrics_update_dispatcher_.timing().Clone();
}

void PageLoadTracker::OnPageEventTimingChanged(uint64_t num_interactions) {
  for (const auto& observer : observers_) {
    observer->OnPageEventTimingUpdate(num_interactions);
  }
}

void PageLoadTracker::OnSubFrameTimingChanged(
    content::RenderFrameHost* rfh,
    const mojom::PageLoadTiming& timing) {
  DCHECK(rfh->GetParentOrOuterDocument());
  const mojom::PaintTimingPtr& paint_timing = timing.paint_timing;
  largest_contentful_paint_handler_.RecordSubFrameTiming(
      *paint_timing->largest_contentful_paint,
      paint_timing->first_input_or_scroll_notified_timestamp, rfh, url_);
  experimental_largest_contentful_paint_handler_.RecordSubFrameTiming(
      *paint_timing->experimental_largest_contentful_paint,
      paint_timing->first_input_or_scroll_notified_timestamp, rfh, url_);
  for (const auto& observer : observers_) {
    observer->OnTimingUpdate(rfh, timing);
  }
}

void PageLoadTracker::OnSubFrameEventTimingChanged(
    content::RenderFrameHost* rfh,
    const std::vector<mojom::EventTimingPtr>& event_timings) {
  DCHECK(rfh->GetParentOrOuterDocument());
  for (const auto& observer : observers_) {
    observer->OnEventTimingUpdate(rfh, event_timings);
  }
}

void PageLoadTracker::OnPageRenderDataChanged(
    const mojom::FrameRenderDataUpdate& render_data,
    bool is_main_frame) {
  for (const auto& observer : observers_) {
    observer->OnPageRenderDataUpdate(render_data, is_main_frame);
  }
}

void PageLoadTracker::OnSubFrameRenderDataChanged(
    content::RenderFrameHost* rfh,
    const mojom::FrameRenderDataUpdate& render_data) {
  DCHECK(rfh->GetParentOrOuterDocument());
  for (const auto& observer : observers_) {
    observer->OnSubFrameRenderDataUpdate(rfh, render_data);
  }
}

void PageLoadTracker::OnMainFrameMetadataChanged() {
  for (const auto& observer : observers_) {
    observer->OnLoadingBehaviorObserved(nullptr,
                                        GetMainFrameMetadata().behavior_flags);
    observer->OnJavaScriptFrameworksObserved(
        nullptr, GetMainFrameMetadata().framework_detection_result);
  }
}

void PageLoadTracker::OnSubframeMetadataChanged(
    content::RenderFrameHost* rfh,
    const mojom::FrameMetadata& metadata) {
  for (const auto& observer : observers_) {
    observer->OnLoadingBehaviorObserved(rfh, metadata.behavior_flags);
  }
}

void PageLoadTracker::OnSoftNavigationFirstContentfulPaint(
    const mojom::SoftNavigationMetrics& soft_navigation_metrics) {
  for (const auto& observer : observers_) {
    observer->OnSoftNavigationFirstContentfulPaint(soft_navigation_metrics);
  }
}

void PageLoadTracker::OnSoftNavigationCompleted(
    const SoftNavigationData& soft_navigation_data) {
  for (const auto& observer : observers_) {
    observer->OnSoftNavigationCompleted(soft_navigation_data);
  }
}

void PageLoadTracker::OnSoftNavigationLargestContentfulPaint(
    uint64_t num_soft_lcps) {
  for (const auto& observer : observers_) {
    observer->OnSoftNavigationLargestContentfulPaint(num_soft_lcps);
  }
}

void PageLoadTracker::UpdateFeaturesUsage(
    content::RenderFrameHost* rfh,
    const std::vector<blink::UseCounterFeature>& new_features) {
  for (const auto& observer : observers_) {
    observer->OnFeaturesUsageObserved(rfh, new_features);
  }
}

void PageLoadTracker::UpdateResourceDataUse(
    content::RenderFrameHost* rfh,
    const std::vector<mojom::ResourceDataUpdatePtr>& resources) {
  resource_tracker_.UpdateResourceDataUse(rfh->GetProcess()->GetDeprecatedID(),
                                          resources);
  for (const auto& observer : observers_) {
    observer->OnResourceDataUseObserved(rfh, resources);
  }
}

void PageLoadTracker::UpdateFrameCpuTiming(content::RenderFrameHost* rfh,
                                           const mojom::CpuTiming& timing) {
  for (const auto& observer : observers_) {
    observer->OnCpuTimingUpdate(rfh, timing);
  }
}

void PageLoadTracker::OnMainFrameRectChanged(const gfx::Rect& main_frame_rect) {
  for (const auto& observer : observers_) {
    observer->OnMainFrameRectChanged(main_frame_rect);
  }
}

void PageLoadTracker::OnMainFrameViewportRectChanged(
    const gfx::Rect& main_frame_viewport_rect) {
  for (const auto& observer : observers_) {
    observer->OnMainFrameViewportRectChanged(main_frame_viewport_rect);
  }
}

void PageLoadTracker::OnMainFrameAdRectsChanged(
    const base::flat_map<int, gfx::Rect>& main_frame_ad_rects) {
  for (const auto& observer : observers_) {
    observer->OnMainFrameAdRectsChanged(main_frame_ad_rects);
  }
}

content::WebContents* PageLoadTracker::GetWebContents() const {
  return web_contents_;
}

base::TimeTicks PageLoadTracker::GetNavigationStart() const {
  return navigation_start_;
}

std::optional<base::TimeDelta>
PageLoadTracker::DurationSinceNavigationStartForTime(
    const std::optional<base::TimeTicks>& time) const {
  std::optional<base::TimeDelta> duration;

  if (!time.has_value()) {
    return duration;
  }

  DCHECK_GE(time.value(), navigation_start_);
  duration = time.value() - navigation_start_;
  return duration;
}

std::optional<base::TimeDelta> PageLoadTracker::GetTimeToFirstBackground()
    const {
  return DurationSinceNavigationStartForTime(first_background_time_);
}

std::optional<base::TimeDelta> PageLoadTracker::GetTimeToFirstForeground()
    const {
  return DurationSinceNavigationStartForTime(first_foreground_time_);
}

const PageLoadMetricsObserverDelegate::BackForwardCacheRestore&
PageLoadTracker::GetBackForwardCacheRestore(size_t index) const {
  return back_forward_cache_restores_[index];
}
size_t PageLoadTracker::GetNumBackForwardCacheRestores() const {
  return back_forward_cache_restores_.size();
}

bool PageLoadTracker::StartedInForeground() const {
  return started_in_foreground_;
}

PageVisibility PageLoadTracker::GetVisibilityAtActivation() const {
  return visibility_at_activation_;
}

bool PageLoadTracker::IsReloadAfterDiscard() const {
  return *is_reload_after_discard_;
}

bool PageLoadTracker::WasPrerenderedThenActivatedInForeground() const {
  return GetVisibilityAtActivation() == PageVisibility::kForeground;
}

PrerenderingState PageLoadTracker::GetPrerenderingState() const {
  return prerendering_state_;
}

std::optional<base::TimeDelta> PageLoadTracker::GetActivationStart() const {
  return activation_start_;
}

const UserInitiatedInfo& PageLoadTracker::GetUserInitiatedInfo() const {
  return user_initiated_info_;
}

const GURL& PageLoadTracker::GetUrl() const {
  return url();
}

const GURL& PageLoadTracker::GetStartUrl() const {
  return start_url_;
}

bool PageLoadTracker::DidCommit() const {
  return did_commit_;
}

PageEndReason PageLoadTracker::GetPageEndReason() const {
  return page_end_reason_;
}

const UserInitiatedInfo& PageLoadTracker::GetPageEndUserInitiatedInfo() const {
  return page_end_user_initiated_info_;
}

std::optional<base::TimeDelta> PageLoadTracker::GetTimeToPageEnd() const {
  if (page_end_reason_ != END_NONE) {
    return DurationSinceNavigationStartForTime(page_end_time_);
  }
  DCHECK(page_end_time_.is_null());
  return std::nullopt;
}

const base::TimeTicks& PageLoadTracker::GetPageEndTime() const {
  return page_end_time_;
}

const mojom::FrameMetadata& PageLoadTracker::GetMainFrameMetadata() const {
  return metrics_update_dispatcher_.main_frame_metadata();
}

const mojom::FrameMetadata& PageLoadTracker::GetSubframeMetadata() const {
  return metrics_update_dispatcher_.subframe_metadata();
}

const PageRenderData& PageLoadTracker::GetPageRenderData() const {
  return metrics_update_dispatcher_.page_render_data();
}

const NormalizedCLSData& PageLoadTracker::GetNormalizedCLSData(
    BfcacheStrategy bfcache_strategy) const {
  return metrics_update_dispatcher_.normalized_cls_data(bfcache_strategy);
}

const InteractionToNextPaintCalculator&
PageLoadTracker::GetInteractionToNextPaintCalculator() const {
  return metrics_update_dispatcher_.interaction_to_next_paint_calculator();
}

const std::optional<blink::SubresourceLoadMetrics>&
PageLoadTracker::GetSubresourceLoadMetrics() const {
  return metrics_update_dispatcher_.subresource_load_metrics();
}

const mojom::FontLoadingMetricsPtr& PageLoadTracker::GetFontLoadingMetrics()
    const {
  return metrics_update_dispatcher_.font_loading_metrics();
}

const PageRenderData& PageLoadTracker::GetMainFrameRenderData() const {
  return metrics_update_dispatcher_.main_frame_render_data();
}

const ui::ScopedVisibilityTracker& PageLoadTracker::GetVisibilityTracker()
    const {
  return visibility_tracker_;
}

const ResourceTracker& PageLoadTracker::GetResourceTracker() const {
  return resource_tracker_;
}

const LargestContentfulPaintHandler&
PageLoadTracker::GetLargestContentfulPaintHandler() const {
  return largest_contentful_paint_handler_;
}

const LargestContentfulPaintHandler&
PageLoadTracker::GetExperimentalLargestContentfulPaintHandler() const {
  return experimental_largest_contentful_paint_handler_;
}

ukm::SourceId PageLoadTracker::GetPageUkmSourceId() const {
  DCHECK_NE(ukm::kInvalidSourceId, source_id_)
      << "GetPageUkmSourceId was called on a prerendered page before its "
         "activation. We should not collect UKM while prerendering pages.";
  return source_id_;
}

ukm::SourceId PageLoadTracker::GetUkmSourceIdForSameDocumentNavigation(
    base::UnguessableToken same_document_metrics_token) const {
  auto it = source_id_by_same_document_metrics_token_.find(
      same_document_metrics_token);
  if (it != source_id_by_same_document_metrics_token_.end()) {
    return it->second;
  }
  return ukm::kInvalidSourceId;
}

bool PageLoadTracker::IsFirstNavigationInWebContents() const {
  return *is_first_navigation_in_web_contents_;
}

NavigationScenario PageLoadTracker::GetNavigationScenario() const {
  return navigation_scenario_;
}

bool PageLoadTracker::IsOriginVisit() const {
  return is_origin_visit_;
}

bool PageLoadTracker::IsTerminalVisit() const {
  return is_terminal_visit_;
}

bool PageLoadTracker::ShouldObserveScheme(std::string_view scheme) const {
  return embedder_interface_->ShouldObserveScheme(scheme);
}

int64_t PageLoadTracker::GetNavigationId() const {
  return navigation_id_;
}

void PageLoadTracker::RecordLinkNavigation() {
  is_terminal_visit_ = false;
}

void PageLoadTracker::OnEnterBackForwardCache() {
  metrics_update_dispatcher_.FlushPendingTimingUpdates();
  metrics_update_dispatcher_.FlushSoftNavigationMetrics();
  // In case of BackForwardCache, invoke and update the
  // PageLoadMetricsUpdateDispatcher before the page is hidden to enable
  // recording metrics that requires the page to be in foreground before
  // entering BackForwardCache on navigation.
  InvokeAndPruneObservers(
      "PageLoadMetricsObserver::OnEnterBackForwardCache",
      base::BindRepeating(
          [](const mojom::PageLoadTiming* timing,
             PageLoadMetricsObserverInterface* observer) {
            return observer->OnEnterBackForwardCache(*timing);
          },
          &metrics_update_dispatcher_.timing()),
      /*permit_forwarding=*/false);
  metrics_update_dispatcher_.UpdateLayoutShiftNormalizationForBfcache();
  metrics_update_dispatcher_.UpdateInteractionToNextPaintCalculatorForBfcache();
  if (GetWebContents()->GetVisibility() == content::Visibility::VISIBLE) {
    PageHidden();
  }
}

void PageLoadTracker::OnRestoreFromBackForwardCache(
    content::NavigationHandle* navigation_handle) {
  DCHECK(!visibility_tracker_.currently_in_foreground());
  bool visible =
      GetWebContents()->GetVisibility() == content::Visibility::VISIBLE;

  BackForwardCacheRestore back_forward_cache_restore(
      visible, navigation_handle->NavigationStart());
  back_forward_cache_restores_.push_back(back_forward_cache_restore);

  if (visible) {
    PageShown();
  }

  for (const auto& observer : observers_) {
    observer->OnRestoreFromBackForwardCache(metrics_update_dispatcher_.timing(),
                                            navigation_handle);
  }

  // Reset the page end reason to END_NONE. The page has been restored, its
  // previous end reason is no longer relevant. Similarly, its page end time is
  // no longer accurate, so reset that as well.
  page_end_reason_ = END_NONE;
  page_end_time_ = base::TimeTicks();
}

void PageLoadTracker::OnSharedStorageWorkletHostCreated() {
  for (const auto& observer : observers_) {
    observer->OnSharedStorageWorkletHostCreated();
  }
}

void PageLoadTracker::OnSharedStorageSelectURLCalled() {
  for (const auto& observer : observers_) {
    observer->OnSharedStorageSelectURLCalled();
  }
}

void PageLoadTracker::UpdateMetrics(
    content::RenderFrameHost* render_frame_host,
    mojom::PageLoadTimingPtr new_timing,
    mojom::FrameMetadataPtr new_metadata,
    const std::vector<blink::UseCounterFeature>& features,
    const std::vector<mojom::ResourceDataUpdatePtr>& resources,
    mojom::FrameRenderDataUpdatePtr render_data,
    mojom::CpuTimingPtr cpu_timing,
    std::vector<mojom::EventTimingPtr> event_timings,
    const std::optional<blink::SubresourceLoadMetrics>&
        subresource_load_metrics,
    std::vector<mojom::SoftNavigationMetricsPtr> soft_navigation_metrics,
    std::vector<mojom::LargestContentfulPaintTimingPtr>
        soft_largest_contentful_paint,
    mojom::FontLoadingMetricsPtr font_loading_metrics) {
  if (parent_tracker_) {
    parent_tracker_->UpdateMetrics(
        render_frame_host, new_timing.Clone(), new_metadata.Clone(), features,
        resources, render_data.Clone(), cpu_timing.Clone(),
        mojo::Clone(event_timings), subresource_load_metrics,
        mojo::Clone(soft_navigation_metrics),
        mojo::Clone(soft_largest_contentful_paint),
        font_loading_metrics.Clone());
  }

  metrics_update_dispatcher_.UpdateMetrics(
      render_frame_host, std::move(new_timing), std::move(new_metadata),
      std::move(features), resources, std::move(render_data),
      std::move(cpu_timing), std::move(event_timings), subresource_load_metrics,
      std::move(soft_navigation_metrics),
      std::move(soft_largest_contentful_paint), std::move(font_loading_metrics),
      page_type_);
}

void PageLoadTracker::AddCustomUserTimings(
    std::vector<mojom::CustomUserTimingMarkPtr> custom_timings) {
  for (const auto& observer : observers_) {
    observer->OnCustomUserTimingMarkObserved(custom_timings);
  }
}

void PageLoadTracker::SetPageMainFrame(content::RenderFrameHost* rfh) {
  page_main_frame_id_ = rfh->GetGlobalId();
}

base::WeakPtr<PageLoadTracker> PageLoadTracker::GetWeakPtr() {
  return weak_factory_.GetWeakPtr();
}

void PageLoadTracker::InvokeAndPruneObservers(
    const char* trace_name,
    PageLoadTracker::InvokeCallback callback,
    bool permit_forwarding) {
  TRACE_EVENT0(TRACE_DISABLED_BY_DEFAULT("loading"), trace_name);
  std::vector<std::unique_ptr<PageLoadMetricsObserverInterface>>
      forward_observers;
  for (auto it = observers_.begin(); it != observers_.end();) {
    auto policy = callback.Run(it->get());
    switch (policy) {
      case PageLoadMetricsObserver::CONTINUE_OBSERVING:
        ++it;
        break;
      case PageLoadMetricsObserver::STOP_OBSERVING:
        if ((*it)->GetObserverName()) {
          observers_map_.erase((*it)->GetObserverName());
        }
        it = observers_.erase(it);
        break;
      case PageLoadMetricsObserver::FORWARD_OBSERVING:
        DCHECK(permit_forwarding);
        DCHECK((*it)->GetObserverName())
            << "GetObserverName should be implemented";
        auto target_observer =
            parent_tracker_
                ? parent_tracker_->FindObserver((*it)->GetObserverName())
                : nullptr;
        if (target_observer) {
          forward_observers.emplace_back(
              std::make_unique<PageLoadMetricsForwardObserver>(
                  target_observer));
        }
        observers_map_.erase((*it)->GetObserverName());
        it = observers_.erase(it);
        break;
    }
  }
  for (auto& observer : forward_observers) {
    DCHECK(observers_map_.find(observer->GetObserverName()) ==
           observers_map_.end());
    AddObserver(std::move(observer));
  }
}

}  // namespace page_load_metrics
