chromium/chrome/browser/apps/app_service/metrics/website_metrics_browsertest.cc

// Copyright 2022 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/apps/app_service/metrics/website_metrics.h"

#include <memory>
#include <optional>
#include <set>

#include "base/command_line.h"
#include "base/containers/contains.h"
#include "base/json/values_util.h"
#include "base/run_loop.h"
#include "base/time/time.h"
#include "build/chromeos_buildflags.h"
#include "chrome/browser/apps/app_service/metrics/website_metrics_browser_test_mixin.h"
#include "chrome/browser/profiles/profile.h"
#include "chrome/browser/profiles/profile_manager.h"
#include "chrome/browser/ui/browser.h"
#include "chrome/browser/ui/browser_commands.h"
#include "chrome/browser/ui/browser_finder.h"
#include "chrome/browser/ui/browser_navigator.h"
#include "chrome/browser/ui/browser_window.h"
#include "chrome/browser/ui/tabs/tab_enums.h"
#include "chrome/browser/ui/tabs/tab_model.h"
#include "chrome/browser/web_applications/test/web_app_install_test_utils.h"
#include "chrome/browser/web_applications/web_app_id_constants.h"
#include "chrome/browser/web_applications/web_app_install_info.h"
#include "chrome/common/chrome_switches.h"
#include "chrome/test/base/mixin_based_in_process_browser_test.h"
#include "chrome/test/base/ui_test_utils.h"
#include "components/history/core/browser/history_types.h"
#include "components/ukm/test_ukm_recorder.h"
#include "components/webapps/browser/banners/installable_web_app_check_result.h"
#include "components/webapps/browser/banners/web_app_banner_data.h"
#include "components/webapps/common/web_app_id.h"
#include "content/public/browser/page_navigator.h"
#include "content/public/browser/web_contents.h"
#include "content/public/test/browser_test.h"
#include "content/public/test/test_utils.h"
#include "testing/gmock/include/gmock/gmock.h"
#include "testing/gtest/include/gtest/gtest.h"
#include "ui/wm/core/window_util.h"
#include "url/gurl.h"

using ::testing::_;
using ::testing::Eq;

namespace apps {

class TestWebsiteMetrics : public WebsiteMetrics {
 public:
  explicit TestWebsiteMetrics(Profile* profile)
      : WebsiteMetrics(profile,
                       /*user_type_by_device_type=*/0) {}

  void AwaitForInstallableWebAppCheck(const GURL& ukm_key) {
    if (on_checked_) {
      return;
    }

    ukm_key_ = ukm_key;

    base::RunLoop loop;
    quit_closure_ = loop.QuitClosure();
    loop.Run();
  }

 private:
  void OnWebContentsUpdated(content::WebContents* web_contents) override {
    WebsiteMetrics::OnWebContentsUpdated(web_contents);
    on_checked_ = false;
  }

  void OnInstallableWebAppStatusUpdated(
      content::WebContents* web_contents,
      webapps::InstallableWebAppCheckResult result,
      const std::optional<webapps::WebAppBannerData>& data) override {
    WebsiteMetrics::OnInstallableWebAppStatusUpdated(web_contents, result,
                                                     data);
    if (webcontents_to_ukm_key_.find(web_contents) ==
            webcontents_to_ukm_key_.end() ||
        webcontents_to_ukm_key_[web_contents] != ukm_key_) {
      return;
    }

    on_checked_ = true;
    if (quit_closure_) {
      std::move(quit_closure_).Run();
    }
  }

  base::OnceClosure quit_closure_;
  bool on_checked_ = false;
  GURL ukm_key_;
};

// Mock observer for the `WebsiteMetrics` component used for testing purposes.
class MockObserver : public WebsiteMetrics::Observer {
 public:
  MockObserver() = default;
  MockObserver(const MockObserver&) = delete;
  MockObserver& operator=(const MockObserver&) = delete;
  ~MockObserver() override = default;

  MOCK_METHOD(void,
              OnUrlOpened,
              (const GURL& gurl, ::content::WebContents* web_contents),
              (override));

  MOCK_METHOD(void,
              OnUrlClosed,
              (const GURL& gurl, ::content::WebContents* web_contents),
              (override));

  MOCK_METHOD(void,
              OnUrlUsage,
              (const GURL& gurl, base::TimeDelta running_time),
              (override));

  MOCK_METHOD(void, OnWebsiteMetricsDestroyed, (), (override));
};

class WebsiteMetricsBrowserTest : public MixinBasedInProcessBrowserTest {
 protected:
  void SetUpOnMainThread() override {
    MixinBasedInProcessBrowserTest::SetUpOnMainThread();
    test_ukm_recorder_ = std::make_unique<ukm::TestAutoSetUkmRecorder>();

    embedded_test_server()->ServeFilesFromSourceDirectory(
        "chrome/test/data/banners");
    ASSERT_TRUE(embedded_test_server()->Start());
  }

  void SetUpCommandLine(base::CommandLine* command_line) override {
    command_line->AppendSwitch(::switches::kNoStartupWindow);
  }

  Browser* CreateBrowser() {
    return website_metrics_browser_test_mixin_.CreateBrowser();
  }

  Browser* CreateAppBrowser(const std::string& app_id) {
    auto params = Browser::CreateParams::CreateForApp(
        "_crx_" + app_id, true /* trusted_source */,
        gfx::Rect(), /* window_bounts */
        profile(), true /* user_gesture */);
    Browser* browser = Browser::Create(params);
    browser->window()->Show();
    return browser;
  }

  ::content::WebContents* InsertForegroundTab(Browser* browser,
                                              const std::string& url) {
    return website_metrics_browser_test_mixin_.InsertForegroundTab(browser,
                                                                   url);
  }

  ::content::WebContents* InsertBackgroundTab(Browser* browser,
                                              const std::string& url) {
    return website_metrics_browser_test_mixin_.InsertBackgroundTab(browser,
                                                                   url);
  }

  void NavigateActiveTab(Browser* browser, const std::string& url) {
    return website_metrics_browser_test_mixin_.NavigateActiveTab(browser, url);
  }

  webapps::AppId InstallWebApp(
      const std::string& start_url,
      web_app::mojom::UserDisplayMode user_display_mode) {
    auto info = web_app::WebAppInstallInfo::CreateWithStartUrlForTesting(
        GURL(start_url));
    info->user_display_mode = user_display_mode;
    auto app_id = web_app::test::InstallWebApp(profile(), std::move(info));
    return app_id;
  }

  webapps::AppId InstallWebAppOpeningAsTab(const std::string& start_url) {
    return InstallWebApp(start_url, web_app::mojom::UserDisplayMode::kBrowser);
  }

  webapps::AppId InstallWebAppOpeningAsWindow(const std::string& start_url) {
    return InstallWebApp(start_url,
                         web_app::mojom::UserDisplayMode::kStandalone);
  }

  void VerifyUrlInfo(const GURL& url, bool is_activated, bool promotable) {
    EXPECT_EQ(is_activated, url_infos()[url].is_activated);
    EXPECT_EQ(promotable, url_infos()[url].promotable);
  }

  void VerifyUrlInfoInPref(const GURL& url, bool promotable) {
    const auto& dict = profile()->GetPrefs()->GetDict(kWebsiteUsageTime);

    const auto* url_info = dict.FindDict(url.spec());
    ASSERT_TRUE(url_info);

    auto promotable_value = url_info->FindBool(kPromotableKey);
    ASSERT_TRUE(promotable_value.has_value());
    EXPECT_EQ(promotable, promotable_value.value());
  }

  void VerifyNoUrlInfoInPref(const GURL& url) {
    const auto& dict = profile()->GetPrefs()->GetDict(kWebsiteUsageTime);

    const auto* url_info = dict.FindDict(url.spec());
    ASSERT_FALSE(url_info);
  }

  void VerifyNoUsageTimeUkm(const GURL& url) {
    const auto entries =
        test_ukm_recorder()->GetEntriesByName("ChromeOS.WebsiteUsageTime");
    int count = 0;
    for (const ukm::mojom::UkmEntry* entry : entries) {
      const ukm::UkmSource* src =
          test_ukm_recorder()->GetSourceForSourceId(entry->source_id);
      if (src == nullptr || src->url() != url) {
        continue;
      }
      ++count;
    }
    ASSERT_EQ(0, count);
  }

  void VerifyUsageTimeUkm(const GURL& url, bool promotable) {
    const auto entries =
        test_ukm_recorder()->GetEntriesByName("ChromeOS.WebsiteUsageTime");
    int count = 0;
    for (const ukm::mojom::UkmEntry* entry : entries) {
      const ukm::UkmSource* src =
          test_ukm_recorder()->GetSourceForSourceId(entry->source_id);
      if (src == nullptr || src->url() != url) {
        continue;
      }
      ++count;
      test_ukm_recorder()->ExpectEntryMetric(entry, "Promotable", promotable);
    }
    ASSERT_EQ(1, count);
  }

  base::flat_map<aura::Window*, content::WebContents*>&
  window_to_web_contents() {
    return website_metrics()->window_to_web_contents_;
  }

  std::map<content::WebContents*,
           std::unique_ptr<WebsiteMetrics::ActiveTabWebContentsObserver>>&
  webcontents_to_observer_map() {
    return website_metrics()->webcontents_to_observer_map_;
  }

  std::map<content::WebContents*, GURL>& webcontents_to_ukm_key() {
    return website_metrics()->webcontents_to_ukm_key_;
  }

  std::map<GURL, WebsiteMetrics::UrlInfo>& url_infos() {
    return website_metrics()->url_infos_;
  }

  ukm::TestAutoSetUkmRecorder* test_ukm_recorder() {
    return test_ukm_recorder_.get();
  }

  Profile* profile() { return ProfileManager::GetPrimaryUserProfile(); }

  WebsiteMetrics* website_metrics() {
    return website_metrics_browser_test_mixin_.website_metrics();
  }

 protected:
  WebsiteMetricsBrowserTestMixin website_metrics_browser_test_mixin_{
      &mixin_host_};
  std::unique_ptr<ukm::TestAutoSetUkmRecorder> test_ukm_recorder_;
};

IN_PROC_BROWSER_TEST_F(WebsiteMetricsBrowserTest, InsertAndCloseTabs) {
  InstallWebAppOpeningAsTab("https://a.example.org");

  Browser* browser = CreateBrowser();
  auto* window = browser->window()->GetNativeWindow();
  EXPECT_EQ(1u, window_to_web_contents().size());

  // Insert an app tab.
  InsertForegroundTab(browser, "https://a.example.org");
  EXPECT_EQ(1u, webcontents_to_observer_map().size());
  EXPECT_TRUE(base::Contains(webcontents_to_observer_map(),
                             window_to_web_contents()[window]));
  EXPECT_EQ(window_to_web_contents()[window]->GetVisibleURL(),
            GURL("https://a.example.org"));
  EXPECT_TRUE(webcontents_to_ukm_key().empty());
  EXPECT_TRUE(url_infos().empty());

  // Open a second tab in foreground with no app.
  auto* tab_app1 = InsertForegroundTab(browser, "https://b.example.org");
  EXPECT_EQ(2u, webcontents_to_observer_map().size());
  EXPECT_TRUE(base::Contains(webcontents_to_observer_map(),
                             window_to_web_contents()[window]));
  EXPECT_EQ(window_to_web_contents()[window]->GetVisibleURL(),
            GURL("https://b.example.org"));
  EXPECT_EQ(1u, webcontents_to_ukm_key().size());
  EXPECT_EQ(webcontents_to_ukm_key()[tab_app1], GURL("https://b.example.org"));
  VerifyUrlInfo(GURL("https://b.example.org"),
                /*is_activated=*/true, /*promotable=*/false);

  // Open two more tabs in foreground and close them.
  auto* tab_app3 = InsertForegroundTab(browser, "https://c.example.org");
  auto* tab_app4 = InsertForegroundTab(browser, "https://d.example.org");

  EXPECT_EQ(4u, webcontents_to_observer_map().size());
  EXPECT_EQ(1u, window_to_web_contents().size());
  EXPECT_TRUE(base::Contains(webcontents_to_observer_map(),
                             window_to_web_contents()[window]));
  EXPECT_EQ(window_to_web_contents()[window]->GetVisibleURL(),
            GURL("https://d.example.org"));
  EXPECT_EQ(3u, webcontents_to_ukm_key().size());
  EXPECT_EQ(webcontents_to_ukm_key()[tab_app3], GURL("https://c.example.org"));
  EXPECT_EQ(webcontents_to_ukm_key()[tab_app4], GURL("https://d.example.org"));
  VerifyUrlInfo(GURL("https://c.example.org"),
                /*is_activated=*/false, /*promotable=*/false);
  VerifyUrlInfo(GURL("https://d.example.org"),
                /*is_activated=*/true, /*promotable=*/false);

  // Close in reverse order.
  int i = browser->tab_strip_model()->GetIndexOfWebContents(tab_app4);
  browser->tab_strip_model()->CloseWebContentsAt(
      i, TabCloseTypes::CLOSE_USER_GESTURE);
  EXPECT_EQ(2u, webcontents_to_ukm_key().size());
  EXPECT_FALSE(base::Contains(webcontents_to_ukm_key(), tab_app4));
  VerifyUrlInfo(GURL("https://c.example.org"),
                /*is_activated=*/true, /*promotable=*/false);

  i = browser->tab_strip_model()->GetIndexOfWebContents(tab_app3);
  browser->tab_strip_model()->CloseWebContentsAt(
      i, TabCloseTypes::CLOSE_USER_GESTURE);
  EXPECT_EQ(2u, webcontents_to_observer_map().size());
  EXPECT_TRUE(base::Contains(webcontents_to_observer_map(),
                             window_to_web_contents()[window]));
  EXPECT_EQ(window_to_web_contents()[window]->GetVisibleURL(),
            GURL("https://b.example.org"));
  EXPECT_EQ(1u, webcontents_to_ukm_key().size());
  EXPECT_FALSE(base::Contains(webcontents_to_ukm_key(), tab_app3));
  VerifyUrlInfo(GURL("https://b.example.org"),
                /*is_activated=*/true, /*promotable=*/false);

  browser->tab_strip_model()->CloseAllTabs();
  EXPECT_TRUE(webcontents_to_observer_map().empty());
  EXPECT_TRUE(window_to_web_contents().empty());
  EXPECT_TRUE(webcontents_to_ukm_key().empty());
  EXPECT_EQ(3u, url_infos().size());
  VerifyUrlInfo(GURL("https://b.example.org"),
                /*is_activated=*/false, /*promotable=*/false);
  website_metrics()->OnFiveMinutes();
  VerifyNoUrlInfoInPref(GURL("https://a.example.org"));
  VerifyUrlInfoInPref(GURL("https://b.example.org"),
                      /*promotable=*/false);
  VerifyUrlInfoInPref(GURL("https://c.example.org"),
                      /*promotable=*/false);
  VerifyUrlInfoInPref(GURL("https://d.example.org"),
                      /*promotable=*/false);

  // Simulate recording the UKMs to clear the local usage time records.
  website_metrics()->OnTwoHours();
  VerifyNoUsageTimeUkm(GURL("https://a.example.org"));
  VerifyUsageTimeUkm(GURL("https://b.example.org"),
                     /*promotable=*/false);
  VerifyUsageTimeUkm(GURL("https://c.example.org"),
                     /*promotable=*/false);
  VerifyUsageTimeUkm(GURL("https://d.example.org"),
                     /*promotable=*/false);
  EXPECT_TRUE(url_infos().empty());
}

IN_PROC_BROWSER_TEST_F(WebsiteMetricsBrowserTest, ForegroundTabNavigate) {
  Browser* browser = CreateBrowser();
  auto* window = browser->window()->GetNativeWindow();
  EXPECT_EQ(1u, window_to_web_contents().size());

  // Open a tab in foreground.
  auto* tab_app = InsertForegroundTab(browser, "https://a.example.org");
  EXPECT_EQ(1u, webcontents_to_observer_map().size());
  EXPECT_TRUE(base::Contains(webcontents_to_observer_map(),
                             window_to_web_contents()[window]));
  EXPECT_EQ(window_to_web_contents()[window]->GetVisibleURL(),
            GURL("https://a.example.org"));
  EXPECT_EQ(1u, webcontents_to_ukm_key().size());
  EXPECT_EQ(webcontents_to_ukm_key()[tab_app], GURL("https://a.example.org"));
  VerifyUrlInfo(GURL("https://a.example.org"),
                /*is_activated=*/true, /*promotable=*/false);

  // Navigate the foreground tab to a different url.
  NavigateActiveTab(browser, "https://b.example.org");

  EXPECT_EQ(1u, webcontents_to_observer_map().size());
  EXPECT_EQ(1u, window_to_web_contents().size());
  EXPECT_TRUE(base::Contains(webcontents_to_observer_map(),
                             window_to_web_contents()[window]));
  EXPECT_EQ(window_to_web_contents()[window]->GetVisibleURL(),
            GURL("https://b.example.org"));
  EXPECT_EQ(1u, webcontents_to_ukm_key().size());
  EXPECT_EQ(webcontents_to_ukm_key()[tab_app], GURL("https://b.example.org"));
  VerifyUrlInfo(GURL("https://a.example.org"),
                /*is_activated=*/false, /*promotable=*/false);
  VerifyUrlInfo(GURL("https://b.example.org"),
                /*is_activated=*/true, /*promotable=*/false);

  website_metrics()->OnFiveMinutes();
  browser->tab_strip_model()->CloseAllTabs();
  EXPECT_TRUE(webcontents_to_observer_map().empty());
  EXPECT_TRUE(window_to_web_contents().empty());
  EXPECT_TRUE(webcontents_to_ukm_key().empty());
  VerifyUrlInfo(GURL("https://a.example.org"),
                /*is_activated=*/false, /*promotable=*/false);
  VerifyUrlInfo(GURL("https://b.example.org"),
                /*is_activated=*/false, /*promotable=*/false);
  VerifyUrlInfoInPref(GURL("https://a.example.org"),
                      /*promotable=*/false);
  VerifyUrlInfoInPref(GURL("https://b.example.org"),
                      /*promotable=*/false);

  // Simulate recording the UKMs to clear the local usage time records.
  website_metrics()->OnTwoHours();
  VerifyUsageTimeUkm(GURL("https://a.example.org"),
                     /*promotable=*/false);
  VerifyUsageTimeUkm(GURL("https://b.example.org"),
                     /*promotable=*/false);
  EXPECT_TRUE(url_infos().empty());
}

IN_PROC_BROWSER_TEST_F(WebsiteMetricsBrowserTest, NavigateToBackgroundTab) {
  auto website_metrics_ptr =
      std::make_unique<apps::TestWebsiteMetrics>(profile());
  auto* const metrics = website_metrics_ptr.get();
  website_metrics_browser_test_mixin_.metrics_service()
      ->SetWebsiteMetricsForTesting(std::move(website_metrics_ptr));

  Browser* browser = CreateBrowser();
  auto* window = browser->window()->GetNativeWindow();
  EXPECT_EQ(1u, window_to_web_contents().size());
  // Open a tab in foreground.
  GURL url1 =
      embedded_test_server()->GetURL("/banners/no_manifest_test_page.html");
  auto* tab1 = InsertForegroundTab(browser, url1.spec());
  EXPECT_EQ(1u, webcontents_to_observer_map().size());
  EXPECT_TRUE(base::Contains(webcontents_to_observer_map(),
                             window_to_web_contents()[window]));
  EXPECT_EQ(window_to_web_contents()[window]->GetVisibleURL(), url1);
  EXPECT_EQ(1u, webcontents_to_ukm_key().size());
  EXPECT_EQ(webcontents_to_ukm_key()[tab1], url1);
  VerifyUrlInfo(url1,
                /*is_activated=*/true, /*promotable=*/false);

  // Navigate the background tab to a url with a manifest.
  GURL url2 =
      embedded_test_server()->GetURL("/banners/manifest_test_page.html");
  auto* tab2 = InsertBackgroundTab(browser, url2.spec());
  metrics->AwaitForInstallableWebAppCheck(url2);
  EXPECT_EQ(2u, webcontents_to_observer_map().size());
  EXPECT_TRUE(base::Contains(webcontents_to_observer_map(), tab2));
  EXPECT_EQ(1u, window_to_web_contents().size());
  EXPECT_EQ(window_to_web_contents()[window]->GetVisibleURL(), url1);
  EXPECT_EQ(2u, webcontents_to_ukm_key().size());
  EXPECT_EQ(webcontents_to_ukm_key()[tab2], url2);
  VerifyUrlInfo(url1,
                /*is_activated=*/true, /*promotable=*/false);
  VerifyUrlInfo(url2,
                /*is_activated=*/false, /*promotable=*/true);

  website_metrics()->OnFiveMinutes();
  browser->tab_strip_model()->CloseAllTabs();
  EXPECT_TRUE(webcontents_to_observer_map().empty());
  EXPECT_TRUE(window_to_web_contents().empty());
  EXPECT_TRUE(webcontents_to_ukm_key().empty());
  VerifyUrlInfo(url1,
                /*is_activated=*/false, /*promotable=*/false);
  VerifyUrlInfo(url2,
                /*is_activated=*/false, /*promotable=*/true);
  VerifyUrlInfoInPref(url1,
                      /*promotable=*/false);
  VerifyNoUrlInfoInPref(url2);

  // Simulate recording the UKMs to clear the local usage time records.
  website_metrics()->OnTwoHours();
  VerifyUsageTimeUkm(url1,
                     /*promotable=*/false);
  VerifyNoUsageTimeUkm(url2);
  EXPECT_TRUE(url_infos().empty());
}

IN_PROC_BROWSER_TEST_F(WebsiteMetricsBrowserTest, ActiveBackgroundTab) {
  auto website_metrics_ptr =
      std::make_unique<apps::TestWebsiteMetrics>(profile());
  auto* const metrics = website_metrics_ptr.get();
  website_metrics_browser_test_mixin_.metrics_service()
      ->SetWebsiteMetricsForTesting(std::move(website_metrics_ptr));

  Browser* browser = CreateBrowser();
  auto* window = browser->window()->GetNativeWindow();
  EXPECT_EQ(1u, window_to_web_contents().size());
  // Open a tab in foreground.
  GURL url1 =
      embedded_test_server()->GetURL("/banners/no_manifest_test_page.html");
  auto* tab1 = InsertForegroundTab(browser, url1.spec());
  EXPECT_EQ(1u, webcontents_to_observer_map().size());
  EXPECT_TRUE(base::Contains(webcontents_to_observer_map(),
                             window_to_web_contents()[window]));
  EXPECT_EQ(window_to_web_contents()[window]->GetVisibleURL(), url1);
  EXPECT_EQ(1u, webcontents_to_ukm_key().size());
  EXPECT_EQ(webcontents_to_ukm_key()[tab1], url1);
  VerifyUrlInfo(url1,
                /*is_activated=*/true, /*promotable=*/false);

  // Navigate the background tab to a url with a manifest.
  GURL url2 =
      embedded_test_server()->GetURL("/banners/manifest_test_page.html");
  auto* tab2 = InsertBackgroundTab(browser, url2.spec());
  metrics->AwaitForInstallableWebAppCheck(url2);
  EXPECT_EQ(2u, webcontents_to_observer_map().size());
  EXPECT_TRUE(base::Contains(webcontents_to_observer_map(), tab2));
  EXPECT_EQ(1u, window_to_web_contents().size());
  EXPECT_EQ(window_to_web_contents()[window]->GetVisibleURL(), url1);
  EXPECT_EQ(2u, webcontents_to_ukm_key().size());
  EXPECT_EQ(webcontents_to_ukm_key()[tab2], url2);
  VerifyUrlInfo(url1,
                /*is_activated=*/true, /*promotable=*/false);
  VerifyUrlInfo(url2,
                /*is_activated=*/false, /*promotable=*/true);
  website_metrics()->OnFiveMinutes();

  browser->tab_strip_model()->ActivateTabAt(1);
  EXPECT_EQ(2u, webcontents_to_observer_map().size());
  EXPECT_EQ(1u, window_to_web_contents().size());
  EXPECT_EQ(window_to_web_contents()[window]->GetVisibleURL(), url2);
  EXPECT_EQ(2u, webcontents_to_ukm_key().size());
  VerifyUrlInfo(url1,
                /*is_activated=*/false, /*promotable=*/false);
  VerifyUrlInfo(url2,
                /*is_activated=*/true, /*promotable=*/true);
  website_metrics()->OnFiveMinutes();

  browser->tab_strip_model()->CloseAllTabs();
  EXPECT_TRUE(webcontents_to_observer_map().empty());
  EXPECT_TRUE(window_to_web_contents().empty());
  EXPECT_TRUE(webcontents_to_ukm_key().empty());
  VerifyUrlInfo(url1,
                /*is_activated=*/false, /*promotable=*/false);
  VerifyUrlInfo(url2,
                /*is_activated=*/false, /*promotable=*/true);
  website_metrics()->OnFiveMinutes();
  VerifyUrlInfoInPref(url1,
                      /*promotable=*/false);
  VerifyUrlInfoInPref(url2,
                      /*promotable=*/true);

  // Simulate recording the UKMs to clear the local usage time records.
  website_metrics()->OnTwoHours();
  VerifyUsageTimeUkm(url1,
                     /*promotable=*/false);
  VerifyUsageTimeUkm(url2,
                     /*promotable=*/true);
  EXPECT_TRUE(url_infos().empty());
}

IN_PROC_BROWSER_TEST_F(WebsiteMetricsBrowserTest, NavigateToUrlWithManifest) {
  auto website_metrics_ptr =
      std::make_unique<apps::TestWebsiteMetrics>(profile());
  auto* const metrics = website_metrics_ptr.get();
  website_metrics_browser_test_mixin_.metrics_service()
      ->SetWebsiteMetricsForTesting(std::move(website_metrics_ptr));

  Browser* browser = CreateBrowser();
  auto* window = browser->window()->GetNativeWindow();
  EXPECT_EQ(1u, window_to_web_contents().size());

  // Open a tab in foreground.
  GURL url1 =
      embedded_test_server()->GetURL("/banners/no_manifest_test_page.html");
  auto* tab_app = InsertForegroundTab(browser, url1.spec());
  EXPECT_EQ(1u, webcontents_to_observer_map().size());
  EXPECT_TRUE(base::Contains(webcontents_to_observer_map(),
                             window_to_web_contents()[window]));
  EXPECT_EQ(window_to_web_contents()[window]->GetVisibleURL(), url1);
  EXPECT_EQ(1u, webcontents_to_ukm_key().size());
  EXPECT_EQ(webcontents_to_ukm_key()[tab_app], url1);
  VerifyUrlInfo(url1,
                /*is_activated=*/true, /*promotable=*/false);

  // Navigate the foreground tab to a url with a manifest.
  GURL url2 =
      embedded_test_server()->GetURL("/banners/manifest_test_page.html");
  NavigateActiveTab(browser, url2.spec());
  metrics->AwaitForInstallableWebAppCheck(url2);
  EXPECT_EQ(1u, webcontents_to_observer_map().size());
  EXPECT_EQ(1u, window_to_web_contents().size());
  EXPECT_TRUE(base::Contains(webcontents_to_observer_map(),
                             window_to_web_contents()[window]));
  EXPECT_EQ(window_to_web_contents()[window]->GetVisibleURL(), url2);
  EXPECT_EQ(1u, webcontents_to_ukm_key().size());
  EXPECT_EQ(webcontents_to_ukm_key()[tab_app], url2);
  VerifyUrlInfo(url1,
                /*is_activated=*/false, /*promotable=*/false);
  VerifyUrlInfo(url2,
                /*is_activated=*/true, /*promotable=*/true);

  browser->tab_strip_model()->CloseAllTabs();
  EXPECT_TRUE(webcontents_to_observer_map().empty());
  EXPECT_TRUE(window_to_web_contents().empty());
  EXPECT_TRUE(webcontents_to_ukm_key().empty());
  VerifyUrlInfo(url1,
                /*is_activated=*/false, /*promotable=*/false);
  VerifyUrlInfo(url2,
                /*is_activated=*/false, /*promotable=*/true);
  website_metrics()->OnFiveMinutes();
  VerifyUrlInfoInPref(url1,
                      /*promotable=*/false);
  VerifyUrlInfoInPref(url2,
                      /*promotable=*/true);

  // Simulate recording the UKMs to clear the local usage time records.
  website_metrics()->OnTwoHours();
  VerifyUsageTimeUkm(url1,
                     /*promotable=*/false);
  VerifyUsageTimeUkm(url2,
                     /*promotable=*/true);
  EXPECT_TRUE(url_infos().empty());
}

IN_PROC_BROWSER_TEST_F(WebsiteMetricsBrowserTest, MultipleBrowser) {
  // Setup: two browsers with two tabs each.
  auto* browser1 = CreateBrowser();
  auto* window1 = browser1->window()->GetNativeWindow();
  auto* tab_app1 = InsertForegroundTab(browser1, "https://a.example.org");
  auto* tab_app2 = InsertForegroundTab(browser1, "https://b.example.org");

  EXPECT_EQ(1u, window_to_web_contents().size());
  EXPECT_EQ(2u, webcontents_to_observer_map().size());
  EXPECT_TRUE(base::Contains(webcontents_to_observer_map(),
                             window_to_web_contents()[window1]));
  EXPECT_EQ(window_to_web_contents()[window1]->GetVisibleURL(),
            GURL("https://b.example.org"));
  EXPECT_EQ(2u, webcontents_to_ukm_key().size());
  EXPECT_EQ(webcontents_to_ukm_key()[tab_app1], GURL("https://a.example.org"));
  EXPECT_EQ(webcontents_to_ukm_key()[tab_app2], GURL("https://b.example.org"));
  VerifyUrlInfo(GURL("https://a.example.org"),
                /*is_activated=*/false, /*promotable=*/false);
  VerifyUrlInfo(GURL("https://b.example.org"),
                /*is_activated=*/true, /*promotable=*/false);

  auto* browser2 = CreateBrowser();
  auto* window2 = browser2->window()->GetNativeWindow();
  auto* tab_app3 = InsertForegroundTab(browser2, "https://c.example.org");
  auto* tab_app4 = InsertForegroundTab(browser2, "https://d.example.org");
  wm::GetActivationClient(window1->GetRootWindow())->DeactivateWindow(window1);

  EXPECT_EQ(2u, window_to_web_contents().size());
  EXPECT_EQ(4u, webcontents_to_observer_map().size());
  EXPECT_TRUE(base::Contains(webcontents_to_observer_map(),
                             window_to_web_contents()[window2]));
  EXPECT_EQ(window_to_web_contents()[window2]->GetVisibleURL(),
            GURL("https://d.example.org"));
  EXPECT_EQ(4u, webcontents_to_ukm_key().size());
  EXPECT_EQ(webcontents_to_ukm_key()[tab_app3], GURL("https://c.example.org"));
  EXPECT_EQ(webcontents_to_ukm_key()[tab_app4], GURL("https://d.example.org"));
  VerifyUrlInfo(GURL("https://b.example.org"),
                /*is_activated=*/false, /*promotable=*/false);
  VerifyUrlInfo(GURL("https://c.example.org"),
                /*is_activated=*/false, /*promotable=*/false);
  VerifyUrlInfo(GURL("https://d.example.org"),
                /*is_activated=*/true, /*promotable=*/false);

  // Close tabs.
  int i = browser1->tab_strip_model()->GetIndexOfWebContents(tab_app1);
  browser1->tab_strip_model()->CloseWebContentsAt(
      i, TabCloseTypes::CLOSE_USER_GESTURE);
  EXPECT_EQ(2u, window_to_web_contents().size());
  EXPECT_EQ(3u, webcontents_to_observer_map().size());
  EXPECT_EQ(window_to_web_contents()[window1]->GetVisibleURL(),
            GURL("https://b.example.org"));
  EXPECT_EQ(3u, webcontents_to_ukm_key().size());
  EXPECT_FALSE(base::Contains(webcontents_to_ukm_key(), tab_app1));
  VerifyUrlInfo(GURL("https://d.example.org"),
                /*is_activated=*/true, /*promotable=*/false);

  i = browser2->tab_strip_model()->GetIndexOfWebContents(tab_app3);
  browser2->tab_strip_model()->CloseWebContentsAt(
      i, TabCloseTypes::CLOSE_USER_GESTURE);
  EXPECT_EQ(2u, window_to_web_contents().size());
  EXPECT_EQ(2u, webcontents_to_observer_map().size());
  EXPECT_EQ(window_to_web_contents()[window2]->GetVisibleURL(),
            GURL("https://d.example.org"));
  EXPECT_EQ(2u, webcontents_to_ukm_key().size());
  EXPECT_FALSE(base::Contains(webcontents_to_ukm_key(), tab_app3));
  VerifyUrlInfo(GURL("https://c.example.org"),
                /*is_activated=*/false, /*promotable=*/false);
  VerifyUrlInfo(GURL("https://d.example.org"),
                /*is_activated=*/true, /*promotable=*/false);

  i = browser2->tab_strip_model()->GetIndexOfWebContents(tab_app4);
  browser2->tab_strip_model()->CloseWebContentsAt(
      i, TabCloseTypes::CLOSE_USER_GESTURE);
  wm::GetActivationClient(window1->GetRootWindow())->ActivateWindow(window1);
  EXPECT_EQ(1u, window_to_web_contents().size());
  EXPECT_EQ(1u, webcontents_to_observer_map().size());
  EXPECT_TRUE(base::Contains(webcontents_to_observer_map(),
                             window_to_web_contents()[window1]));
  EXPECT_EQ(1u, webcontents_to_ukm_key().size());
  EXPECT_FALSE(base::Contains(webcontents_to_ukm_key(), tab_app4));
  VerifyUrlInfo(GURL("https://b.example.org"),
                /*is_activated=*/true, /*promotable=*/false);
  VerifyUrlInfo(GURL("https://d.example.org"),
                /*is_activated=*/false, /*promotable=*/false);

  i = browser1->tab_strip_model()->GetIndexOfWebContents(tab_app2);
  browser1->tab_strip_model()->CloseWebContentsAt(
      i, TabCloseTypes::CLOSE_USER_GESTURE);
  VerifyUrlInfo(GURL("https://a.example.org"),
                /*is_activated=*/false, /*promotable=*/false);
  VerifyUrlInfo(GURL("https://b.example.org"),
                /*is_activated=*/false, /*promotable=*/false);

  EXPECT_TRUE(window_to_web_contents().empty());
  EXPECT_TRUE(webcontents_to_observer_map().empty());
  EXPECT_TRUE(webcontents_to_ukm_key().empty());

  website_metrics()->OnFiveMinutes();
  VerifyUrlInfoInPref(GURL("https://a.example.org"),
                      /*promotable=*/false);
  VerifyUrlInfoInPref(GURL("https://b.example.org"),
                      /*promotable=*/false);
  VerifyUrlInfoInPref(GURL("https://c.example.org"),
                      /*promotable=*/false);
  VerifyUrlInfoInPref(GURL("https://d.example.org"),
                      /*promotable=*/false);

  // Simulate recording the UKMs to clear the local usage time records.
  website_metrics()->OnTwoHours();
  VerifyUsageTimeUkm(GURL("https://a.example.org"),
                     /*promotable=*/false);
  VerifyUsageTimeUkm(GURL("https://b.example.org"),
                     /*promotable=*/false);
  VerifyUsageTimeUkm(GURL("https://c.example.org"),
                     /*promotable=*/false);
  VerifyUsageTimeUkm(GURL("https://d.example.org"),
                     /*promotable=*/false);
  EXPECT_TRUE(url_infos().empty());
}

// TODO(crbug.com/40910130): Test is flaky.
#if BUILDFLAG(IS_CHROMEOS_LACROS)
#define MAYBE_MoveActivatedTabToNewBrowser DISABLED_MoveActivatedTabToNewBrowser
#else
#define MAYBE_MoveActivatedTabToNewBrowser MoveActivatedTabToNewBrowser
#endif
IN_PROC_BROWSER_TEST_F(WebsiteMetricsBrowserTest,
                       MAYBE_MoveActivatedTabToNewBrowser) {
  auto website_metrics_ptr =
      std::make_unique<apps::TestWebsiteMetrics>(profile());
  auto* const metrics = website_metrics_ptr.get();
  website_metrics_browser_test_mixin_.metrics_service()
      ->SetWebsiteMetricsForTesting(std::move(website_metrics_ptr));

  // Create a browser with two tabs.
  auto* browser1 = CreateBrowser();
  auto* window1 = browser1->window()->GetNativeWindow();

  // Open a tab in foreground with a manifest.
  GURL url1 =
      embedded_test_server()->GetURL("/banners/manifest_test_page.html");
  auto* tab1 = InsertForegroundTab(browser1, url1.spec());
  metrics->AwaitForInstallableWebAppCheck(url1);
  // Open a background tab to a url.
  GURL url2 =
      embedded_test_server()->GetURL("/banners/no_manifest_test_page.html");
  auto* tab2 = InsertBackgroundTab(browser1, url2.spec());

  EXPECT_EQ(2u, webcontents_to_observer_map().size());
  EXPECT_TRUE(base::Contains(webcontents_to_observer_map(),
                             window_to_web_contents()[window1]));
  EXPECT_EQ(window_to_web_contents()[window1]->GetVisibleURL(), url1);
  EXPECT_EQ(2u, webcontents_to_ukm_key().size());
  EXPECT_EQ(webcontents_to_ukm_key()[tab1], url1);
  EXPECT_EQ(webcontents_to_ukm_key()[tab2], url2);
  VerifyUrlInfo(url1,
                /*is_activated=*/true, /*promotable=*/true);
  VerifyUrlInfo(url2,
                /*is_activated=*/false, /*promotable=*/false);

  website_metrics()->OnFiveMinutes();
  VerifyUrlInfoInPref(url1,
                      /*promotable=*/true);

  // Create the second browser, and move the activated tab to the new browser.
  auto* browser2 = CreateBrowser();
  auto* window2 = browser2->window()->GetNativeWindow();
  wm::GetActivationClient(window1->GetRootWindow())->DeactivateWindow(window1);

  // Detach `tab1`.
  std::unique_ptr<tabs::TabModel> detached_tab =
      browser1->tab_strip_model()->DetachTabAtForInsertion(0);

  // Attach `tab1` to `browser2`.
  browser2->tab_strip_model()->InsertDetachedTabAt(0, std::move(detached_tab),
                                                   AddTabTypes::ADD_ACTIVE);
  auto* tab3 = browser2->tab_strip_model()->GetWebContentsAt(0);

  EXPECT_EQ(2u, window_to_web_contents().size());
  EXPECT_EQ(2u, webcontents_to_observer_map().size());
  EXPECT_TRUE(base::Contains(webcontents_to_observer_map(),
                             window_to_web_contents()[window2]));
  EXPECT_EQ(window_to_web_contents()[window2]->GetVisibleURL(), url1);
  EXPECT_EQ(2u, webcontents_to_ukm_key().size());
  EXPECT_EQ(webcontents_to_ukm_key()[tab3], url1);
  EXPECT_EQ(webcontents_to_ukm_key()[tab2], url2);
  VerifyUrlInfo(url1,
                /*is_activated=*/true, /*promotable=*/true);
  VerifyUrlInfo(url2,
                /*is_activated=*/false, /*promotable=*/false);

  website_metrics()->OnFiveMinutes();
  VerifyUrlInfoInPref(url1,
                      /*promotable=*/true);
  VerifyNoUrlInfoInPref(url2);

  auto* tab4 = InsertForegroundTab(browser2, "https://a.example.org");
  EXPECT_EQ(2u, window_to_web_contents().size());
  EXPECT_EQ(3u, webcontents_to_observer_map().size());
  EXPECT_TRUE(base::Contains(webcontents_to_observer_map(),
                             window_to_web_contents()[window2]));
  EXPECT_EQ(window_to_web_contents()[window2]->GetVisibleURL(),
            GURL("https://a.example.org"));
  EXPECT_EQ(3u, webcontents_to_ukm_key().size());
  EXPECT_EQ(webcontents_to_ukm_key()[tab4], GURL("https://a.example.org"));
  VerifyUrlInfo(GURL("https://a.example.org"),
                /*is_activated=*/true, /*promotable=*/false);
  VerifyUrlInfo(url1,
                /*is_activated=*/false, /*promotable=*/true);
  VerifyUrlInfo(url2,
                /*is_activated=*/false, /*promotable=*/false);

  auto i = browser2->tab_strip_model()->GetIndexOfWebContents(tab4);
  browser2->tab_strip_model()->CloseWebContentsAt(
      i, TabCloseTypes::CLOSE_USER_GESTURE);

  // Simulate recording the UKMs to clear the local usage time records.
  website_metrics()->OnFiveMinutes();
  website_metrics()->OnTwoHours();
  VerifyUsageTimeUkm(GURL("https://a.example.org"),
                     /*promotable=*/false);
  VerifyUsageTimeUkm(url1,
                     /*promotable=*/true);
  VerifyNoUsageTimeUkm(url2);

  browser2->tab_strip_model()->CloseAllTabs();
  wm::GetActivationClient(window1->GetRootWindow())->ActivateWindow(window1);
  EXPECT_EQ(1u, window_to_web_contents().size());
  EXPECT_EQ(1u, webcontents_to_observer_map().size());
  EXPECT_TRUE(base::Contains(webcontents_to_observer_map(),
                             window_to_web_contents()[window1]));
  EXPECT_EQ(1u, webcontents_to_ukm_key().size());
  EXPECT_TRUE(base::Contains(webcontents_to_ukm_key(), tab2));

  browser1->tab_strip_model()->CloseAllTabs();
  EXPECT_TRUE(window_to_web_contents().empty());
  EXPECT_TRUE(webcontents_to_observer_map().empty());
  EXPECT_TRUE(webcontents_to_ukm_key().empty());
  VerifyUrlInfo(url1,
                /*is_activated=*/false, /*promotable=*/true);
  VerifyUrlInfo(url2,
                /*is_activated=*/false, /*promotable=*/false);

  EXPECT_TRUE(window_to_web_contents().empty());
  EXPECT_TRUE(webcontents_to_observer_map().empty());
  EXPECT_TRUE(webcontents_to_ukm_key().empty());

  website_metrics()->OnFiveMinutes();
  VerifyUrlInfoInPref(url1,
                      /*promotable=*/true);
  VerifyUrlInfoInPref(url2,
                      /*promotable=*/false);

  // Simulate recording the UKMs to clear the local usage time records.
  website_metrics()->OnTwoHours();
  EXPECT_TRUE(url_infos().empty());
}

IN_PROC_BROWSER_TEST_F(WebsiteMetricsBrowserTest,
                       MoveInActivatedTabToNewBrowser) {
  // Create a browser with two tabs.
  auto* browser1 = CreateBrowser();
  auto* window1 = browser1->window()->GetNativeWindow();
  auto* tab1 = InsertForegroundTab(browser1, "https://a.example.org");
  auto* tab2 = InsertBackgroundTab(browser1, "https://b.example.org");

  EXPECT_EQ(1u, window_to_web_contents().size());
  EXPECT_EQ(2u, webcontents_to_observer_map().size());
  EXPECT_TRUE(base::Contains(webcontents_to_observer_map(),
                             window_to_web_contents()[window1]));
  EXPECT_EQ(window_to_web_contents()[window1]->GetVisibleURL(),
            GURL("https://a.example.org"));
  EXPECT_EQ(2u, webcontents_to_ukm_key().size());
  EXPECT_EQ(webcontents_to_ukm_key()[tab1], GURL("https://a.example.org"));
  EXPECT_EQ(webcontents_to_ukm_key()[tab2], GURL("https://b.example.org"));
  VerifyUrlInfo(GURL("https://a.example.org"),
                /*is_activated=*/true, /*promotable=*/false);
  VerifyUrlInfo(GURL("https://b.example.org"),
                /*is_activated=*/false, /*promotable=*/false);

  website_metrics()->OnFiveMinutes();
  VerifyUrlInfoInPref(GURL("https://a.example.org"),
                      /*promotable=*/false);
  VerifyNoUrlInfoInPref(GURL("https://b.example.org"));

  // Simulate recording the UKMs to clear the local usage time records.
  website_metrics()->OnTwoHours();
  VerifyUsageTimeUkm(GURL("https://a.example.org"),
                     /*promotable=*/false);
  VerifyNoUsageTimeUkm(GURL("https://b.example.org"));

  // Create the second browser, and move the inactivated tab to the new browser.
  auto* browser2 = CreateBrowser();
  auto* window2 = browser2->window()->GetNativeWindow();
  wm::GetActivationClient(window1->GetRootWindow())->DeactivateWindow(window1);

  // Detach `tab2`.
  std::unique_ptr<tabs::TabModel> detached_tab =
      browser1->tab_strip_model()->DetachTabAtForInsertion(1);

  // Attach `tab2` to `browser2`.
  browser2->tab_strip_model()->InsertDetachedTabAt(0, std::move(detached_tab),
                                                   AddTabTypes::ADD_ACTIVE);
  auto* tab3 = browser2->tab_strip_model()->GetWebContentsAt(0);

  EXPECT_EQ(2u, window_to_web_contents().size());
  EXPECT_EQ(2u, webcontents_to_observer_map().size());
  EXPECT_TRUE(base::Contains(webcontents_to_observer_map(),
                             window_to_web_contents()[window2]));
  EXPECT_EQ(window_to_web_contents()[window2]->GetVisibleURL(),
            GURL("https://b.example.org"));
  EXPECT_EQ(2u, webcontents_to_ukm_key().size());
  EXPECT_EQ(webcontents_to_ukm_key()[tab1], GURL("https://a.example.org"));
  EXPECT_EQ(webcontents_to_ukm_key()[tab3], GURL("https://b.example.org"));
  VerifyUrlInfo(GURL("https://a.example.org"),
                /*is_activated=*/false, /*promotable=*/false);
  VerifyUrlInfo(GURL("https://b.example.org"),
                /*is_activated=*/true, /*promotable=*/false);

  website_metrics()->OnFiveMinutes();
  VerifyUrlInfoInPref(GURL("https://b.example.org"),
                      /*promotable=*/false);

  // Simulate recording the UKMs to clear the local usage time records.
  website_metrics()->OnTwoHours();
  VerifyUsageTimeUkm(GURL("https://b.example.org"),
                     /*promotable=*/false);

  browser1->tab_strip_model()->CloseAllTabs();
  EXPECT_EQ(1u, window_to_web_contents().size());
  EXPECT_EQ(1u, webcontents_to_observer_map().size());
  EXPECT_TRUE(base::Contains(webcontents_to_observer_map(),
                             window_to_web_contents()[window2]));
  EXPECT_EQ(1u, webcontents_to_ukm_key().size());
  EXPECT_TRUE(base::Contains(webcontents_to_ukm_key(), tab3));

  browser2->tab_strip_model()->CloseAllTabs();
  EXPECT_TRUE(window_to_web_contents().empty());
  EXPECT_TRUE(webcontents_to_observer_map().empty());
  EXPECT_TRUE(webcontents_to_ukm_key().empty());
  VerifyUrlInfo(GURL("https://a.example.org"),
                /*is_activated=*/false, /*promotable=*/false);
  VerifyUrlInfo(GURL("https://b.example.org"),
                /*is_activated=*/false, /*promotable=*/false);

  EXPECT_TRUE(window_to_web_contents().empty());
  EXPECT_TRUE(webcontents_to_observer_map().empty());
  EXPECT_TRUE(webcontents_to_ukm_key().empty());

  website_metrics()->OnFiveMinutes();
  VerifyNoUrlInfoInPref(GURL("https://a.example.org"));
  VerifyUrlInfoInPref(GURL("https://b.example.org"),
                      /*promotable=*/false);

  // Simulate recording the UKMs to clear the local usage time records.
  website_metrics()->OnTwoHours();
  EXPECT_TRUE(url_infos().empty());
}

IN_PROC_BROWSER_TEST_F(WebsiteMetricsBrowserTest, WindowedWebApp) {
  std::string app_id = InstallWebAppOpeningAsWindow("https://d.example.org");

  // Open app D in a window (configured to open in a window).
  Browser* browser = CreateAppBrowser(app_id);
  InsertForegroundTab(browser, "https://d.example.org");

  // Verify there is no window, web contents recorded.
  EXPECT_TRUE(window_to_web_contents().empty());
  EXPECT_TRUE(webcontents_to_observer_map().empty());
  EXPECT_TRUE(webcontents_to_ukm_key().empty());
  EXPECT_TRUE(url_infos().empty());

  // Close the browser.
  browser->tab_strip_model()->CloseAllTabs();

  EXPECT_TRUE(window_to_web_contents().empty());
  EXPECT_TRUE(webcontents_to_observer_map().empty());
  EXPECT_TRUE(webcontents_to_ukm_key().empty());
}

IN_PROC_BROWSER_TEST_F(WebsiteMetricsBrowserTest, OnHistoryDeletions) {
  // Setup: two browsers with one tabs each.
  auto* browser1 = CreateBrowser();
  auto* window1 = browser1->window()->GetNativeWindow();
  auto* tab_app1 = InsertForegroundTab(browser1, "https://a.example.org");

  auto* browser2 = CreateBrowser();
  auto* window2 = browser2->window()->GetNativeWindow();
  auto* tab_app2 = InsertForegroundTab(browser2, "https://b.example.org");
  wm::GetActivationClient(window1->GetRootWindow())->DeactivateWindow(window1);

  EXPECT_EQ(2u, window_to_web_contents().size());
  EXPECT_EQ(2u, webcontents_to_observer_map().size());
  EXPECT_TRUE(base::Contains(webcontents_to_observer_map(),
                             window_to_web_contents()[window1]));
  EXPECT_TRUE(base::Contains(webcontents_to_observer_map(),
                             window_to_web_contents()[window2]));
  EXPECT_EQ(window_to_web_contents()[window1]->GetVisibleURL(),
            GURL("https://a.example.org"));
  EXPECT_EQ(window_to_web_contents()[window2]->GetVisibleURL(),
            GURL("https://b.example.org"));
  EXPECT_EQ(2u, webcontents_to_ukm_key().size());
  EXPECT_EQ(webcontents_to_ukm_key()[tab_app1], GURL("https://a.example.org"));
  EXPECT_EQ(webcontents_to_ukm_key()[tab_app2], GURL("https://b.example.org"));
  VerifyUrlInfo(GURL("https://a.example.org"),
                /*is_activated=*/false, /*promotable=*/false);
  VerifyUrlInfo(GURL("https://b.example.org"),
                /*is_activated=*/true, /*promotable=*/false);

  // Simulate OnHistoryDeletions is called for an expiration. Nothing should be
  // cleared.
  auto info = history::DeletionInfo(
      history::DeletionTimeRange(base::Time(), base::Time::Now()),
      /*is_from_expiration=*/true, {}, {}, std::optional<std::set<GURL>>());
  website_metrics()->OnHistoryDeletions(nullptr, info);
  EXPECT_EQ(2u, window_to_web_contents().size());
  EXPECT_EQ(2u, webcontents_to_observer_map().size());
  EXPECT_TRUE(base::Contains(webcontents_to_observer_map(),
                             window_to_web_contents()[window1]));
  EXPECT_TRUE(base::Contains(webcontents_to_observer_map(),
                             window_to_web_contents()[window2]));
  EXPECT_EQ(window_to_web_contents()[window1]->GetVisibleURL(),
            GURL("https://a.example.org"));
  EXPECT_EQ(window_to_web_contents()[window2]->GetVisibleURL(),
            GURL("https://b.example.org"));
  EXPECT_EQ(2u, webcontents_to_ukm_key().size());
  EXPECT_EQ(webcontents_to_ukm_key()[tab_app1], GURL("https://a.example.org"));
  EXPECT_EQ(webcontents_to_ukm_key()[tab_app2], GURL("https://b.example.org"));
  VerifyUrlInfo(GURL("https://a.example.org"),
                /*is_activated=*/false, /*promotable=*/false);
  VerifyUrlInfo(GURL("https://b.example.org"),
                /*is_activated=*/true, /*promotable=*/false);

  // Persist data to prefs and verify.
  website_metrics()->OnFiveMinutes();
  VerifyUrlInfoInPref(GURL("https://a.example.org"),
                      /*promotable=*/false);
  VerifyUrlInfoInPref(GURL("https://b.example.org"),
                      /*promotable=*/false);

  // Simulate OnHistoryDeletions again for an expiration. The prefs should not
  // be affected
  website_metrics()->OnHistoryDeletions(nullptr, info);
  EXPECT_EQ(2u, webcontents_to_ukm_key().size());
  EXPECT_EQ(2u, url_infos().size());
  EXPECT_EQ(webcontents_to_ukm_key()[tab_app1], GURL("https://a.example.org"));
  EXPECT_EQ(webcontents_to_ukm_key()[tab_app2], GURL("https://b.example.org"));
  VerifyUrlInfoInPref(GURL("https://a.example.org"),
                      /*promotable=*/false);
  VerifyUrlInfoInPref(GURL("https://b.example.org"),
                      /*promotable=*/false);

  // Simulate OnHistoryDeletions for a non-expiration and ensure prefs and
  // in-memory usage data is cleared.
  website_metrics()->OnHistoryDeletions(nullptr,
                                        history::DeletionInfo::ForAllHistory());
  EXPECT_EQ(2u, window_to_web_contents().size());
  EXPECT_EQ(2u, webcontents_to_observer_map().size());
  EXPECT_TRUE(webcontents_to_ukm_key().empty());
  EXPECT_TRUE(url_infos().empty());
  VerifyNoUrlInfoInPref(GURL("https://a.example.org"));
  VerifyNoUrlInfoInPref(GURL("https://b.example.org"));

  // Create 2 tabs for the 2 browsers separately.
  auto* tab_app3 = InsertForegroundTab(browser1, "https://c.example.org");
  auto* tab_app4 = InsertForegroundTab(browser2, "https://d.example.org");

  EXPECT_EQ(2u, window_to_web_contents().size());
  EXPECT_EQ(4u, webcontents_to_observer_map().size());
  EXPECT_EQ(window_to_web_contents()[window1]->GetVisibleURL(),
            GURL("https://c.example.org"));
  EXPECT_EQ(window_to_web_contents()[window2]->GetVisibleURL(),
            GURL("https://d.example.org"));
  EXPECT_EQ(2u, webcontents_to_ukm_key().size());
  EXPECT_EQ(webcontents_to_ukm_key()[tab_app3], GURL("https://c.example.org"));
  EXPECT_EQ(webcontents_to_ukm_key()[tab_app4], GURL("https://d.example.org"));
  VerifyUrlInfo(GURL("https://c.example.org"),
                /*is_activated=*/false, /*promotable=*/false);
  VerifyUrlInfo(GURL("https://d.example.org"),
                /*is_activated=*/true, /*promotable=*/false);
  website_metrics()->OnFiveMinutes();
  // "https://c.example.org" is inactivated, and the running time is zero, so it
  // won't be saved in the user pref.
  VerifyNoUrlInfoInPref(GURL("https://c.example.org"));
  VerifyUrlInfoInPref(GURL("https://d.example.org"),
                      /*promotable=*/false);

  // Close the browsers.
  browser1->tab_strip_model()->CloseAllTabs();
  browser2->tab_strip_model()->CloseAllTabs();

  EXPECT_TRUE(window_to_web_contents().empty());
  EXPECT_TRUE(webcontents_to_observer_map().empty());
  EXPECT_TRUE(webcontents_to_ukm_key().empty());

  // Simulate recording the UKMs to clear the local usage time records.
  website_metrics()->OnTwoHours();
  VerifyNoUsageTimeUkm(GURL("https://a.example.org"));
  VerifyNoUsageTimeUkm(GURL("https://b.example.org"));
  VerifyNoUsageTimeUkm(GURL("https://c.example.org"));
  VerifyUsageTimeUkm(GURL("https://d.example.org"),
                     /*promotable=*/false);
  EXPECT_TRUE(url_infos().empty());
}

class WebsiteMetricsObserverBrowserTest : public WebsiteMetricsBrowserTest {
 protected:
  void TearDownOnMainThread() override {
    // Unregister observer to prevent noise during teardown.
    website_metrics()->RemoveObserver(&observer_);
    WebsiteMetricsBrowserTest::TearDownOnMainThread();
  }

  MockObserver observer_;
};

IN_PROC_BROWSER_TEST_F(WebsiteMetricsObserverBrowserTest, NotifyOnUrlOpened) {
  const std::string& kUrl = "https://a.example.org";
  auto* const browser = CreateBrowser();
  auto* const window = browser->window()->GetNativeWindow();

  website_metrics()->AddObserver(&observer_);
  EXPECT_CALL(observer_, OnUrlOpened)
      .WillOnce([&](const GURL& url, ::content::WebContents* web_contents) {
        EXPECT_THAT(url, Eq(GURL(kUrl)));
        EXPECT_THAT(web_contents, Eq(window_to_web_contents()[window]));
      });
  InsertForegroundTab(browser, kUrl);
}

IN_PROC_BROWSER_TEST_F(WebsiteMetricsObserverBrowserTest,
                       NotifyOnBackgroundUrlOpened) {
  const std::string& kUrl = "https://a.example.org";
  auto* const browser = CreateBrowser();
  NavigateActiveTab(browser, kUrl);
  website_metrics()->AddObserver(&observer_);

  const std::string& kBackgroundUrl = "https://b.example.org";
  EXPECT_CALL(observer_, OnUrlOpened(GURL(kBackgroundUrl), _)).Times(1);
  InsertBackgroundTab(browser, kBackgroundUrl);
}

IN_PROC_BROWSER_TEST_F(WebsiteMetricsObserverBrowserTest,
                       NotifyUrlOpenedClosedOnContentNavigation) {
  const std::string& kOldUrl = "https://a.example.org";
  auto* const browser = CreateBrowser();
  auto* const window = browser->window()->GetNativeWindow();
  NavigateActiveTab(browser, kOldUrl);
  website_metrics()->AddObserver(&observer_);

  // Navigate to a different URL and verify observer is notified.
  const std::string& kNewUrl = "https://b.example.org";
  ::content::WebContents* const active_web_contents =
      window_to_web_contents()[window];
  EXPECT_CALL(observer_, OnUrlOpened(GURL(kNewUrl), active_web_contents))
      .Times(1);
  EXPECT_CALL(observer_, OnUrlClosed(GURL(kOldUrl), active_web_contents))
      .Times(1);
  NavigateActiveTab(browser, kNewUrl);
}

IN_PROC_BROWSER_TEST_F(WebsiteMetricsObserverBrowserTest,
                       NotifyUrlClosedOnContentNavigationToRegisteredWebApp) {
  const std::string& kWebAppUrl = "https://b.example.org";
  InstallWebAppOpeningAsTab(kWebAppUrl);

  auto* const browser = CreateBrowser();
  auto* const window = browser->window()->GetNativeWindow();
  const std::string& kOldUrl = "https://a.example.org";
  NavigateActiveTab(browser, kOldUrl);
  website_metrics()->AddObserver(&observer_);

  // Navigate to the web app and verify observer is notified of URL close.
  const auto window_it = window_to_web_contents().find(window);
  ASSERT_NE(window_it, window_to_web_contents().end());
  ::content::WebContents* const active_web_contents = window_it->second;
  EXPECT_CALL(observer_, OnUrlClosed(GURL(kOldUrl), active_web_contents))
      .Times(1);
  NavigateActiveTab(browser, kWebAppUrl);
}

IN_PROC_BROWSER_TEST_F(WebsiteMetricsObserverBrowserTest,
                       NotifyUrlOpenedOnContentNavigationFromRegisteredWebApp) {
  const std::string& kWebAppUrl = "https://b.example.org";
  InstallWebAppOpeningAsTab(kWebAppUrl);
  website_metrics()->AddObserver(&observer_);

  auto* const browser = CreateBrowser();
  auto* const window = browser->window()->GetNativeWindow();
  NavigateActiveTab(browser, kWebAppUrl);

  // Navigate to the URL from the web app and verify observer is notified of URL
  // open.
  const std::string& kNewUrl = "https://a.example.org";
  const auto window_it = window_to_web_contents().find(window);
  ASSERT_NE(window_it, window_to_web_contents().end());
  ::content::WebContents* const active_web_contents = window_it->second;
  EXPECT_CALL(observer_, OnUrlOpened(GURL(kNewUrl), active_web_contents))
      .Times(1);
  NavigateActiveTab(browser, kNewUrl);
}

IN_PROC_BROWSER_TEST_F(WebsiteMetricsObserverBrowserTest,
                       DoNotNotifyIfUrlAlreadyOpenInRenderFrame) {
  const std::string& kUrl = "https://a.example.org";
  auto* const browser = CreateBrowser();
  NavigateActiveTab(browser, kUrl);
  website_metrics()->AddObserver(&observer_);

  EXPECT_CALL(observer_, OnUrlOpened).Times(0);
  NavigateActiveTab(browser, kUrl);
}

IN_PROC_BROWSER_TEST_F(WebsiteMetricsObserverBrowserTest,
                       NotifyUrlClosedOnTabClose) {
  const std::string& kUrl = "https://a.example.org";
  auto* const browser = CreateBrowser();
  auto* const window = browser->window()->GetNativeWindow();
  NavigateActiveTab(browser, kUrl);
  website_metrics()->AddObserver(&observer_);

  // Close the tab and verify observer is notified.
  EXPECT_CALL(observer_,
              OnUrlClosed(GURL(kUrl), window_to_web_contents()[window]))
      .Times(1);
  browser->tab_strip_model()->CloseAllTabs();
}

IN_PROC_BROWSER_TEST_F(WebsiteMetricsObserverBrowserTest,
                       NotifyUrlClosedOnWindowClose) {
  // Open URLs in two separate tabs.
  const std::string& kUrl1 = "https://a.example.org";
  const std::string& kUrl2 = "https://b.example.org";
  auto* const browser = CreateBrowser();
  auto* const window = browser->window()->GetNativeWindow();
  NavigateActiveTab(browser, kUrl1);
  InsertBackgroundTab(browser, kUrl2);
  website_metrics()->AddObserver(&observer_);

  // Simulate window closure and verify observer is notified accordingly.
  const std::string& kNewUrl = "https://b.example.org";
  EXPECT_CALL(observer_,
              OnUrlClosed(GURL(kUrl1), window_to_web_contents()[window]))
      .Times(1);
  EXPECT_CALL(observer_, OnUrlClosed(GURL(kUrl2), _)).Times(1);
  browser->tab_strip_model()->CloseAllTabs();
}

IN_PROC_BROWSER_TEST_F(WebsiteMetricsObserverBrowserTest,
                       NotifyOnWebsiteMetricsDestroyed) {
  // Test with a separate instance of website metrics so we do not affect
  // pre-existing test teardown fixtures.
  std::unique_ptr<WebsiteMetrics> owned_website_metrics =
      std::make_unique<WebsiteMetrics>(profile(),
                                       /*user_type_by_device_type=*/0);
  owned_website_metrics->AddObserver(&observer_);
  EXPECT_CALL(observer_, OnWebsiteMetricsDestroyed).Times(1);
  owned_website_metrics.reset();
}

IN_PROC_BROWSER_TEST_F(WebsiteMetricsObserverBrowserTest, NotifyOnUrlUsage) {
  const std::string& kUrl = "https://a.example.org";
  auto* const browser = CreateBrowser();
  NavigateActiveTab(browser, kUrl);
  website_metrics()->AddObserver(&observer_);

  EXPECT_CALL(observer_, OnUrlUsage)
      .WillOnce([&](const GURL& url, base::TimeDelta running_time) {
        EXPECT_THAT(url, Eq(GURL(kUrl)));
        EXPECT_TRUE(running_time.is_positive());
      });
  website_metrics()->OnFiveMinutes();
}

IN_PROC_BROWSER_TEST_F(WebsiteMetricsObserverBrowserTest,
                       DoNotNotifyBackgroundUrlUsage) {
  const std::string& kUrl = "https://a.example.org";
  const std::string& kBackgroundUrl = "https://b.example.org";
  auto* const browser = CreateBrowser();
  NavigateActiveTab(browser, kUrl);
  InsertBackgroundTab(browser, kBackgroundUrl);
  website_metrics()->AddObserver(&observer_);

  EXPECT_CALL(observer_, OnUrlUsage(GURL(kUrl), _)).Times(1);
  EXPECT_CALL(observer_, OnUrlUsage(GURL(kBackgroundUrl), _)).Times(0);
  website_metrics()->OnFiveMinutes();
}

IN_PROC_BROWSER_TEST_F(WebsiteMetricsObserverBrowserTest, NoUrlUsage) {
  CreateBrowser();
  website_metrics()->AddObserver(&observer_);

  // Verify observer is not notified because there is no web content usage.
  EXPECT_CALL(observer_, OnUrlUsage).Times(0);
  website_metrics()->OnFiveMinutes();
}

}  // namespace apps