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

#include "chrome/browser/page_load_metrics/page_load_metrics_initialize.h"

#include <memory>
#include <string>
#include <utility>

#include "base/functional/bind.h"
#include "build/build_config.h"
#include "build/buildflag.h"
#include "chrome/browser/after_startup_task_utils.h"
#include "chrome/browser/browser_process.h"
#include "components/page_load_metrics/browser/observers/core/uma_page_load_metrics_observer.h"

#if !BUILDFLAG(IS_ANDROID)
#include "chrome/browser/ui/browser_window/public/browser_window_interface.h"
#include "chrome/browser/ui/browser_window/public/global_browser_collection.h"
#include "chrome/browser/ui/tabs/tab_strip_model.h"
#include "components/tabs/public/tab_interface.h"
#include "content/public/browser/navigation_controller.h"
#include "content/public/browser/navigation_entry.h"
#include "content/public/browser/restore_type.h"
#endif
#include "chrome/browser/heavy_ad_intervention/heavy_ad_service_factory.h"
#include "chrome/browser/history/history_service_factory.h"
#include "chrome/browser/page_load_metrics/observers/bookmark_bar_page_load_metrics_observer.h"
#include "chrome/browser/page_load_metrics/observers/captcha_metrics_observer.h"
#include "chrome/browser/page_load_metrics/observers/chrome_gws_abandoned_page_load_metrics_observer.h"
#include "chrome/browser/page_load_metrics/observers/chrome_gws_page_load_metrics_observer.h"
#include "chrome/browser/page_load_metrics/observers/core/amp_page_load_metrics_observer.h"
#include "chrome/browser/page_load_metrics/observers/core/ukm_page_load_metrics_observer.h"
#include "chrome/browser/page_load_metrics/observers/document_write_page_load_metrics_observer.h"
#include "chrome/browser/page_load_metrics/observers/foreground_duration_ukm_observer.h"
#include "chrome/browser/page_load_metrics/observers/from_gws_page_load_metrics_observer.h"
#include "chrome/browser/page_load_metrics/observers/gws_hp_page_load_metrics_observer.h"
#include "chrome/browser/page_load_metrics/observers/https_engagement_metrics/https_engagement_page_load_metrics_observer.h"
#include "chrome/browser/page_load_metrics/observers/javascript_frameworks_ukm_observer.h"
#include "chrome/browser/page_load_metrics/observers/lcp_critical_path_predictor_page_load_metrics_observer.h"
#include "chrome/browser/page_load_metrics/observers/loading_predictor_page_load_metrics_observer.h"
#include "chrome/browser/page_load_metrics/observers/multi_tab_loading_page_load_metrics_observer.h"
#include "chrome/browser/page_load_metrics/observers/navigation_initiator_page_load_metrics_observer.h"
#include "chrome/browser/page_load_metrics/observers/new_tab_page_initiated_page_load_metrics_observer.h"
#include "chrome/browser/page_load_metrics/observers/new_tab_page_page_load_metrics_observer.h"
#include "chrome/browser/page_load_metrics/observers/omnibox_suggestion_used_page_load_metrics_observer.h"
#include "chrome/browser/page_load_metrics/observers/optimization_guide_page_load_metrics_observer.h"
#include "chrome/browser/page_load_metrics/observers/page_anchors_metrics_observer.h"
#include "chrome/browser/page_load_metrics/observers/protocol_page_load_metrics_observer.h"
#include "chrome/browser/page_load_metrics/observers/scheme_page_load_metrics_observer.h"
#include "chrome/browser/page_load_metrics/observers/security_state_page_load_metrics_observer.h"
#include "chrome/browser/page_load_metrics/observers/signed_exchange_page_load_metrics_observer.h"
#include "chrome/browser/page_load_metrics/observers/tab_strip_page_load_metrics_observer.h"
#include "chrome/browser/page_load_metrics/observers/translate_page_load_metrics_observer.h"
#include "chrome/browser/page_load_metrics/observers/webui_page_load_metrics_observer.h"
#include "chrome/browser/preloading/prefetch/no_state_prefetch/chrome_no_state_prefetch_contents_delegate.h"
#include "chrome/browser/profiles/profile.h"
#include "chrome/browser/search/search.h"
#include "chrome/browser/ui/waap/waap_utils.h"
#include "components/no_state_prefetch/browser/no_state_prefetch_contents.h"
#include "components/page_load_metrics/browser/features.h"
#include "components/page_load_metrics/browser/metrics_web_contents_observer.h"
#include "components/page_load_metrics/browser/navigation_scenario.h"
#include "components/page_load_metrics/browser/observers/ad_metrics/ads_page_load_metrics_observer.h"
#include "components/page_load_metrics/browser/observers/paid_content_page_load_metrics_observer.h"
#include "components/page_load_metrics/browser/observers/preload_serving_metrics_page_load_metrics_observer.h"
#include "components/page_load_metrics/browser/observers/third_party_metrics_observer.h"
#include "components/page_load_metrics/browser/observers/zstd_page_load_metrics_observer.h"
#include "components/page_load_metrics/browser/page_load_metrics_embedder_base.h"
#include "components/page_load_metrics/browser/page_load_tracker.h"
#include "components/page_load_metrics/google/browser/from_gws_abandoned_page_load_metrics_observer.h"
#include "components/page_load_metrics/google/browser/gws_abandoned_page_load_metrics_observer.h"
#include "components/page_load_metrics/google/browser/gws_prewarm_page_load_metrics_observer.h"
#include "content/public/browser/internal_webui_config.h"
#include "content/public/browser/preload_serving_metrics_capsule.h"
#include "content/public/browser/web_contents.h"
#include "content/public/browser/webui_config_map.h"
#include "content/public/common/url_utils.h"
#include "extensions/buildflags/buildflags.h"
#include "third_party/blink/public/common/loader/lcp_critical_path_predictor_util.h"
#include "ui/accessibility/accessibility_features.h"
#include "url/gurl.h"

#if BUILDFLAG(IS_WIN) || BUILDFLAG(IS_MAC) || BUILDFLAG(IS_LINUX) || \
    BUILDFLAG(IS_CHROMEOS)
#include "components/webapps/isolated_web_apps/scheme.h"
#endif  // BUILDFLAG(IS_WIN) || BUILDFLAG(IS_MAC) || BUILDFLAG(IS_LINUX) ||
        // BUILDFLAG(IS_CHROMEOS)

#if BUILDFLAG(IS_ANDROID)
#include "chrome/browser/page_load_metrics/observers/android_page_load_metrics_observer.h"
#endif

#if !BUILDFLAG(IS_ANDROID)
#include "chrome/browser/page_load_metrics/observers/initial_webui_page_load_metrics_observer.h"
#include "chrome/browser/page_load_metrics/observers/non_tab_webui_page_load_metrics_observer.h"
#include "chrome/browser/page_load_metrics/observers/top_chrome_webui_metrics_observer.h"
#include "chrome/browser/ui/read_anything/read_anything_soft_navigation_observer.h"
#include "chrome/browser/ui/waap/waap_utils.h"
#include "chrome/browser/ui/webui/omnibox_popup/omnibox_popup_ui.h"
#include "chrome/browser/ui/webui/top_chrome/top_chrome_webui_config.h"
#include "chrome/common/webui_url_constants.h"
#endif

#if BUILDFLAG(IS_CHROMEOS)
#include "chrome/browser/page_load_metrics/observers/ash_session_restore_page_load_metrics_observer.h"
#endif

#if BUILDFLAG(ENABLE_EXTENSIONS_CORE)
#include "chrome/browser/page_load_metrics/observers/serp_page_load_metrics_observer.h"
#include "extensions/common/constants.h"
#endif

namespace {
#if !BUILDFLAG(IS_ANDROID)
bool IsNonTabWebUI(content::BrowserContext* browser_context, const GURL& url) {
  return TopChromeWebUIConfig::From(browser_context, url) != nullptr;
}

std::string_view GetNonTabWebUIName(content::BrowserContext* browser_context,
                                    const GURL& url) {
  CHECK(IsNonTabWebUI(browser_context, url));
  return TopChromeWebUIConfig::From(browser_context, url)->GetWebUIName();
}
#else
bool IsNonTabWebUI(content::BrowserContext* browser_context, const GURL& url) {
  return false;
}
#endif

std::string GetApplicationLocale() {
  return g_browser_process->GetApplicationLocale();
}

class PageLoadMetricsEmbedder
    : public page_load_metrics::PageLoadMetricsEmbedderBase {
 public:
  explicit PageLoadMetricsEmbedder(content::WebContents* web_contents);

  PageLoadMetricsEmbedder(const PageLoadMetricsEmbedder&) = delete;
  PageLoadMetricsEmbedder& operator=(const PageLoadMetricsEmbedder&) = delete;

  ~PageLoadMetricsEmbedder() override;

  // page_load_metrics::PageLoadMetricsEmbedderBase:
  bool IsNewTabPageUrl(const GURL& url) override;
  bool IsNoStatePrefetch(content::WebContents* web_contents) override;
  bool IsExtensionUrl(const GURL& url) override;
  bool HasWebUIConfig(const GURL& url) override;
  bool IsNonTabWebUI(const GURL& url) override;
  bool IsInternalWebUI(const GURL& url) override;
  bool ShouldObserveScheme(std::string_view scheme) override;
  page_load_metrics::NavigationScenario GetNavigationScenario(
      content::NavigationHandle* navigation_handle) const override;

 protected:
  // page_load_metrics::PageLoadMetricsEmbedderBase:
  void RegisterObservers(page_load_metrics::PageLoadTracker* tracker,
                         content::NavigationHandle* navigation_handle) override;
};

PageLoadMetricsEmbedder::PageLoadMetricsEmbedder(
    content::WebContents* web_contents)
    : PageLoadMetricsEmbedderBase(web_contents) {}

PageLoadMetricsEmbedder::~PageLoadMetricsEmbedder() = default;

void PageLoadMetricsEmbedder::RegisterObservers(
    page_load_metrics::PageLoadTracker* tracker,
    content::NavigationHandle* navigation_handle) {
  // Page-specific observers.
  //
  // Other observers don't get installed because they measure things that
  // don't apply to this type of page, rely on invariants that aren't true
  // about WebUI chrome pages (such as visibility-related things),
  // or because they depend on objects that don't exist for WebUI pages
  // (namely `TabHelper`s).

  if (HasWebUIConfig(navigation_handle->GetURL()) &&
      !IsInternalWebUI(navigation_handle->GetURL())) {
    tracker->AddObserver(std::make_unique<WebUIPageLoadMetricsObserver>());
  }

#if !BUILDFLAG(IS_ANDROID)
  if (HasWebUIConfig(navigation_handle->GetURL()) &&
      waap::IsForInitialWebUI(navigation_handle->GetURL())) {
    tracker->AddObserver(
        std::make_unique<InitialWebUIPageLoadMetricsObserver>());
  }
#endif

#if !BUILDFLAG(IS_ANDROID)
  if (IsNonTabWebUI(navigation_handle->GetURL())) {
    // This embedder is for a non-tab chrome:// page.
    tracker->AddObserver(std::make_unique<NonTabPageLoadMetricsObserver>(
        std::string(GetNonTabWebUIName(web_contents()->GetBrowserContext(),
                                       navigation_handle->GetURL()))));
    if (navigation_handle->GetURL().host() ==
        chrome::kChromeUIOmniboxPopupHost) {
      tracker->AddObserver(std::make_unique<TopChromeWebUIMetricsObserver>(
          std::string(OmniboxPopupUI::GetWebUIName())));
    }
    return;
  }
#endif

  if (IsNewTabPageUrl(navigation_handle->GetURL())) {
    tracker->AddObserver(std::make_unique<NewTabPagePageLoadMetricsObserver>());
    return;
  }

  if (content::HasWebUIScheme(navigation_handle->GetURL())) {
    // No other observers are added for WebUI pages.
    return;
  }

  // Observers for regular web pages.
  RegisterCommonObservers(tracker);

  if (!IsNoStatePrefetch(web_contents())) {
    tracker->AddObserver(std::make_unique<AMPPageLoadMetricsObserver>());
    tracker->AddObserver(std::make_unique<JavascriptFrameworksUkmObserver>());
    tracker->AddObserver(std::make_unique<SchemePageLoadMetricsObserver>());
    tracker->AddObserver(std::make_unique<FromGWSPageLoadMetricsObserver>());
    tracker->AddObserver(std::make_unique<ChromeGWSPageLoadMetricsObserver>());
    tracker->AddObserver(
        std::make_unique<FromGWSAbandonedPageLoadMetricsObserver>());
    tracker->AddObserver(std::make_unique<AbandonedPageLoadMetricsObserver>());
    tracker->AddObserver(
        std::make_unique<ChromeGWSAbandonedPageLoadMetricsObserver>());
    tracker->AddObserver(std::make_unique<GWSHpPageLoadMetricsObserver>());
    tracker->AddObserver(std::make_unique<GWSPrewarmPageLoadMetricsObserver>());
    tracker->AddObserver(std::make_unique<ForegroundDurationUKMObserver>());
    tracker->AddObserver(
        std::make_unique<DocumentWritePageLoadMetricsObserver>());
    tracker->AddObserver(
        std::make_unique<MultiTabLoadingPageLoadMetricsObserver>());
    tracker->AddObserver(
        std::make_unique<OptimizationGuidePageLoadMetricsObserver>());
    tracker->AddObserver(
        std::make_unique<SignedExchangePageLoadMetricsObserver>());
    tracker->AddObserver(
        std::make_unique<HttpsEngagementPageLoadMetricsObserver>(
            web_contents()->GetBrowserContext()));
    tracker->AddObserver(
        std::make_unique<NavigationInitiatorPageLoadMetricsObserver>());
    tracker->AddObserver(std::make_unique<ProtocolPageLoadMetricsObserver>());

    bool is_in_foreground =
        tracker->GetVisibilityTracker().currently_in_foreground();
    std::unique_ptr<page_load_metrics::AdsPageLoadMetricsObserver>
        ads_observer =
            page_load_metrics::AdsPageLoadMetricsObserver::CreateIfNeeded(
                tracker->GetWebContents(),
                HeavyAdServiceFactory::GetForBrowserContext(
                    tracker->GetWebContents()->GetBrowserContext()),
                HistoryServiceFactory::GetForProfile(
                    Profile::FromBrowserContext(
                        web_contents()->GetBrowserContext()),
                    ServiceAccessType::EXPLICIT_ACCESS),
                base::BindRepeating(&GetApplicationLocale), is_in_foreground);
    if (ads_observer) {
      tracker->AddObserver(std::move(ads_observer));
    }

    tracker->AddObserver(std::make_unique<ThirdPartyMetricsObserver>());
    tracker->AddObserver(
        std::make_unique<PaidContentPageLoadMetricsObserver>());

    std::unique_ptr<page_load_metrics::PageLoadMetricsObserver> ukm_observer =
        UkmPageLoadMetricsObserver::CreateIfNeeded();
    if (ukm_observer) {
      tracker->AddObserver(std::move(ukm_observer));
    }

#if BUILDFLAG(IS_ANDROID)
    tracker->AddObserver(std::make_unique<AndroidPageLoadMetricsObserver>());
#endif  // BUILDFLAG(IS_ANDROID)
    std::unique_ptr<page_load_metrics::PageLoadMetricsObserver>
        loading_predictor_observer =
            LoadingPredictorPageLoadMetricsObserver::CreateIfNeeded(
                web_contents());
    if (loading_predictor_observer) {
      tracker->AddObserver(std::move(loading_predictor_observer));
    }
    if (blink::LcppEnabled()) {
      tracker->AddObserver(
          std::make_unique<LcpCriticalPathPredictorPageLoadMetricsObserver>());
    }
    tracker->AddObserver(
        std::make_unique<TabStripPageLoadMetricsObserver>(web_contents()));
    tracker->AddObserver(std::make_unique<BookmarkBarMetricsObserver>());
    tracker->AddObserver(
        std::make_unique<NewTabPageInitiatedPageLoadMetricsObserver>());
    tracker->AddObserver(
        std::make_unique<PreloadServingMetricsPageLoadMetricsObserver>());
  }
  tracker->AddObserver(
      std::make_unique<OmniboxSuggestionUsedMetricsObserver>());
  tracker->AddObserver(
      SecurityStatePageLoadMetricsObserver::MaybeCreateForProfile(
          web_contents()->GetBrowserContext()));
#if BUILDFLAG(ENABLE_EXTENSIONS_CORE)
  tracker->AddObserver(std::make_unique<SerpPageLoadMetricsObserver>());
#endif
  tracker->AddObserver(
      std::make_unique<PageAnchorsMetricsObserver>(tracker->GetWebContents()));
  std::unique_ptr<TranslatePageLoadMetricsObserver> translate_observer =
      TranslatePageLoadMetricsObserver::CreateIfNeeded(
          tracker->GetWebContents());
  if (translate_observer) {
    tracker->AddObserver(std::move(translate_observer));
  }
  tracker->AddObserver(std::make_unique<ZstdPageLoadMetricsObserver>());

#if !BUILDFLAG(IS_ANDROID)
  if (features::IsImmersiveReadAnythingEnabled()) {
    tracker->AddObserver(
        std::make_unique<ReadAnythingSoftNavigationObserver>());
  }
#endif

#if BUILDFLAG(IS_CHROMEOS)
  if (AshSessionRestorePageLoadMetricsObserver::ShouldBeInstantiated(
          Profile::FromBrowserContext(web_contents()->GetBrowserContext()))) {
    tracker->AddObserver(
        std::make_unique<AshSessionRestorePageLoadMetricsObserver>(
            web_contents()));
  }
#endif

  tracker->AddObserver(std::make_unique<CaptchaMetricsObserver>());
}

page_load_metrics::NavigationScenario
PageLoadMetricsEmbedder::GetNavigationScenario(
    content::NavigationHandle* navigation_handle) const {
#if BUILDFLAG(IS_ANDROID)
  return page_load_metrics::NavigationScenario::kUnknown;
#else
  // Slicing by navigation scenario is currently only evaluated after startup.
  if (!AfterStartupTaskUtils::IsBrowserStartupComplete()) {
    return page_load_metrics::NavigationScenario::kStartup;
  }

  // Navigations in WebContents detached from or not associated with any browser
  // window, e.g. background pages or prerender hosts, cannot determine
  // navigation scenario.
  content::WebContents* contents = web_contents();
  BrowserWindowInterface* browser =
      GlobalBrowserCollection::GetInstance()->FindBrowserWithTab(contents);
  if (!browser) {
    return page_load_metrics::NavigationScenario::kUnknown;
  }

  // Discarded tab reloads represent existing tab reactivation rather than new
  // window creation.
  if (navigation_handle->ExistingDocumentWasDiscarded()) {
    return page_load_metrics::NavigationScenario::kSameWindow;
  }

  // If this WebContents previously committed an entry and is not undergoing a
  // session restore, this is a subsequent navigation in an existing tab.
  content::NavigationEntry* last_entry =
      contents->GetController().GetLastCommittedEntry();
  if (last_entry && !last_entry->GetURL().is_empty() &&
      navigation_handle->GetRestoreType() ==
          content::RestoreType::kNotRestored) {
    return page_load_metrics::NavigationScenario::kSameWindow;
  }

  // WebContents without a corresponding tab interface, such as non-tab UI
  // hosts, are treated as same window navigations.
  tabs::TabInterface* tab = tabs::TabInterface::MaybeGetFromContents(contents);
  if (!tab) {
    return page_load_metrics::NavigationScenario::kSameWindow;
  }

  // Browser windows lacking a tab strip, such as custom popups or app panels,
  // default to same window classification.
  TabStripModel* tab_strip = browser->tab_strip_model();
  if (!tab_strip) {
    return page_load_metrics::NavigationScenario::kSameWindow;
  }

  // If the tab strip is empty, in the process of initial window creation, or
  // contains only this initial tab, this qualifies as a new window navigation.
  if (tab_strip->empty() ||
      (tab_strip->count() == 1 &&
       (tab_strip->GetTabAtIndex(0) == tab ||
        tab_strip->GetWebContentsAt(0) == tab->GetContents()))) {
    return page_load_metrics::NavigationScenario::kNewWindow;
  }

  return page_load_metrics::NavigationScenario::kSameWindow;
#endif  // BUILDFLAG(IS_ANDROID)
}

bool PageLoadMetricsEmbedder::IsNewTabPageUrl(const GURL& url) {
  Profile* profile =
      Profile::FromBrowserContext(web_contents()->GetBrowserContext());
  if (!profile) {
    return false;
  }
  return search::IsInstantNTPURL(url, profile);
}

bool PageLoadMetricsEmbedder::IsNoStatePrefetch(
    content::WebContents* web_contents) {
  return prerender::ChromeNoStatePrefetchContentsDelegate::FromWebContents(
      web_contents);
}

bool PageLoadMetricsEmbedder::IsExtensionUrl(const GURL& url) {
#if BUILDFLAG(ENABLE_EXTENSIONS_CORE)
  return url.SchemeIs(extensions::kExtensionScheme);
#else
  return false;
#endif
}

bool PageLoadMetricsEmbedder::HasWebUIConfig(const GURL& url) {
  return content::WebUIConfigMap::GetInstance().GetConfig(
             web_contents()->GetBrowserContext(), url) != nullptr;
}

bool PageLoadMetricsEmbedder::IsNonTabWebUI(const GURL& url) {
  return ::IsNonTabWebUI(web_contents()->GetBrowserContext(), url);
}

bool PageLoadMetricsEmbedder::IsInternalWebUI(const GURL& url) {
  return content::IsInternalWebUI(url);
}

bool PageLoadMetricsEmbedder::ShouldObserveScheme(std::string_view scheme) {
#if BUILDFLAG(IS_WIN) || BUILDFLAG(IS_MAC) || BUILDFLAG(IS_LINUX) || \
    BUILDFLAG(IS_CHROMEOS)
  return scheme == webapps::kIsolatedAppScheme;
#else   // !(BUILDFLAG(IS_WIN) || BUILDFLAG(IS_MAC) || BUILDFLAG(IS_LINUX) ||
        // BUILDFLAG(IS_CHROMEOS))
  return false;
#endif  // BUILDFLAG(IS_WIN) || BUILDFLAG(IS_MAC) || BUILDFLAG(IS_LINUX) ||
        // BUILDFLAG(IS_CHROMEOS)
}

}  // namespace

void InitializePageLoadMetricsForWebContents(
    content::WebContents* web_contents) {
  // Change this method? consider to modify the peer in
  // android_webview/browser/page_load_metrics/page_load_metrics_initialize.cc
  // as well.
  page_load_metrics::MetricsWebContentsObserver::CreateForWebContents(
      web_contents, std::make_unique<PageLoadMetricsEmbedder>(web_contents));
}
