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

#include "base/byte_size.h"
#include "base/strings/utf_string_conversions.h"
#include "base/test/scoped_feature_list.h"
#include "base/time/time.h"
#include "build/build_config.h"
#include "chrome/app/chrome_command_ids.h"
#include "chrome/browser/browser_process.h"
#include "chrome/browser/interstitials/security_interstitial_page_test_utils.h"
#include "chrome/browser/ui/browser_command_controller.h"
#include "chrome/browser/ui/browser_commands.h"
#include "chrome/browser/ui/browser_element_identifiers.h"
#include "chrome/browser/ui/browser_window/public/browser_window_interface.h"
#include "chrome/browser/ui/browser_window/public/browser_window_interface_iterator.h"
#include "chrome/browser/ui/browser_window/public/global_browser_collection.h"
#include "chrome/browser/ui/interaction/browser_elements.h"
#include "chrome/browser/ui/performance_controls/memory_saver_utils.h"
#include "chrome/browser/ui/performance_controls/tab_resource_usage_tab_helper.h"
#include "chrome/browser/ui/performance_controls/test_support/memory_metrics_refresh_waiter.h"
#include "chrome/browser/ui/performance_controls/test_support/memory_saver_interactive_test_mixin.h"
#include "chrome/browser/ui/tabs/saved_tab_groups/collaboration_messaging_tab_data.h"
#include "chrome/browser/ui/ui_features.h"
#include "chrome/browser/ui/views/tabs/hovercard/fade_footer_view.h"
#include "chrome/browser/ui/views/tabs/hovercard/fade_label_view.h"
#include "chrome/browser/ui/views/tabs/hovercard/tab_hover_card_bubble_view.h"
#include "chrome/browser/ui/views/tabs/hovercard/tab_hover_card_controller.h"
#include "chrome/browser/ui/views/tabs/hovercard/tab_hover_card_test_util.h"
#include "chrome/browser/ui/views/tabs/tab.h"
#include "chrome/browser/ui/views/tabs/tab/tab_close_button.h"
#include "chrome/browser/ui/views/tabs/tab_strip.h"
#include "chrome/browser/ui/views/test/tab_strip_interactive_test_mixin.h"
#include "chrome/browser/ui/web_applications/test/web_app_browsertest_util.h"
#include "chrome/browser/web_applications/test/web_app_install_test_utils.h"
#include "chrome/common/pref_names.h"
#include "chrome/common/webui_url_constants.h"
#include "chrome/grit/generated_resources.h"
#include "chrome/test/base/in_process_browser_test.h"
#include "chrome/test/base/interactive_test_utils.h"
#include "chrome/test/base/ui_test_utils.h"
#include "chrome/test/interaction/interactive_browser_test.h"
#include "components/collaboration/public/messaging/message.h"
#include "components/data_sharing/public/features.h"
#include "components/data_sharing/public/group_data.h"
#include "components/lookalikes/core/safety_tip_test_utils.h"
#include "components/performance_manager/public/decorators/process_metrics_decorator.h"
#include "components/performance_manager/public/performance_manager.h"
#include "components/strings/grit/components_strings.h"
#include "components/tabs/public/tab_alert.h"
#include "content/public/browser/web_contents.h"
#include "content/public/common/content_features.h"
#include "content/public/test/browser_test.h"
#include "content/public/test/browser_test_utils.h"
#include "net/base/url_util.h"
#include "net/dns/mock_host_resolver.h"
#include "ui/base/l10n/l10n_util.h"
#include "ui/base/models/image_model.h"
#include "ui/base/models/list_selection_model.h"
#include "ui/base/test/ui_controls.h"
#include "ui/base/text/bytes_formatting.h"
#include "ui/display/display.h"
#include "ui/events/event.h"
#include "ui/events/event_constants.h"
#include "ui/events/types/event_type.h"
#include "ui/gfx/geometry/point.h"
#include "ui/views/controls/label.h"
#include "ui/views/test/widget_activation_waiter.h"
#include "ui/views/test/widget_test.h"
#include "ui/views/view_utils.h"
#include "url/gurl.h"

#if BUILDFLAG(IS_CHROMEOS)
#include "chrome/browser/ash/system_web_apps/test_support/test_system_web_app_installation.h"
#endif

namespace {
constexpr char16_t kTabTitle[] = u"Test Tab 2";
constexpr char16_t kTabDomain[] = u"example.com";
constexpr char kTabUrl[] = "http://example.com/path/to/document.html";

DEFINE_LOCAL_ELEMENT_IDENTIFIER_VALUE(kFirstTabContents);
DEFINE_LOCAL_ELEMENT_IDENTIFIER_VALUE(kSecondTabContents);
DEFINE_LOCAL_ELEMENT_IDENTIFIER_VALUE(kThirdTabContents);

tabs::TabData MakeTabData() {
  tabs::TabData new_tab_data = tabs::TabData();
  new_tab_data.title = kTabTitle;
  new_tab_data.last_committed_url = GURL(kTabUrl);
  new_tab_data.alert_state = {tabs::TabAlert::kAudioPlaying};
  return new_tab_data;
}

collaboration::messaging::PersistentMessage CreateMessage(
    std::string given_name,
    std::string avatar_url,
    collaboration::messaging::CollaborationEvent event) {
  data_sharing::GroupMember member;
  member.given_name = given_name;
  member.avatar_url = GURL(avatar_url);

  collaboration::messaging::MessageAttribution attribution;
  attribution.triggering_user = member;

  collaboration::messaging::PersistentMessage message;
  message.type = collaboration::messaging::PersistentNotificationType::CHIP;
  message.attribution = attribution;
  message.collaboration_event = event;

  return message;
}

}  // namespace

class TabHoverCardInteractiveUiTest
    : public TabStripInteractiveTestMixin<
          MemorySaverInteractiveTestMixin<InteractiveBrowserTest>>,
      public test::TabHoverCardTestUtil {
 public:
  TabHoverCardInteractiveUiTest() {
    scoped_feature_list_.InitWithFeatures(
        {features::kTabHoverCardImages,
         data_sharing::features::kDataSharingFeature,
         features::kTabGroupHoverCards, features::kTabStripDeclutter},
        {});
  }

  ~TabHoverCardInteractiveUiTest() override = default;

  void SetUp() override {
    set_open_about_blank_on_browser_launch(true);
    MemorySaverInteractiveTestMixin::SetUp();
  }

  // Start the test by moving the mouse to a location where it will not be
  // hovering the tabstrip. All subsequent interactions will be simulated.
  void SetUpOnMainThread() override {
    MemorySaverInteractiveTestMixin::SetUpOnMainThread();
    Tab::SetShowHoverCardOnMouseHoverForTesting(true);
    base::RunLoop run_loop(base::RunLoop::Type::kNestableTasksAllowed);
    gfx::Point upper_left;
#if BUILDFLAG(IS_LINUX) || BUILDFLAG(IS_CHROMEOS)
    // Because Ozone makes it impossible to target a point not in a window in
    // tests, instead target the extreme upper left of the browser window.
    upper_left = browser()->GetWindow()->GetBounds().origin();
#endif
    ui_controls::SendMouseMoveNotifyWhenDone(upper_left.x(), upper_left.y(),
                                             run_loop.QuitClosure());
    run_loop.Run();
  }

  void TearDownOnMainThread() override {
    MemorySaverInteractiveTestMixin::TearDownOnMainThread();
  }

  auto UnhoverTarget() { return Steps(MoveMouseTo(kNewTabButtonElementId)); }

  StepBuilder CheckHovercardIsOpen() {
    return WaitForShow(TabHoverCardBubbleView::kHoverCardBubbleElementId);
  }

  StepBuilder CheckHovercardIsClosed() {
    return WaitForHide(TabHoverCardBubbleView::kHoverCardBubbleElementId);
  }

  TabResourceUsageTabHelper* GetResourceUsageAt(int index) {
    return TabResourceUsageTabHelper::From(
        browser()->GetTabStripModel()->GetTabAtIndex(index));
  }

  void SetTabData(int index, tabs::TabData data) {
    if (base::FeatureList::IsEnabled(tabs::kTabStripUnification)) {
      BrowserView* const browser_view =
          BrowserView::GetBrowserViewForBrowser(browser());
      if (auto* tab_view = views::AsViewClass<TabView>(
              browser_view->tab_strip_view()->GetTabAnchorView(
                  browser()
                      ->GetTabStripModel()
                      ->GetTabAtIndex(index)
                      ->GetHandle()))) {
        tab_view->SetDataForTesting(std::move(data));
      }
    } else {
      TabStrip* const tab_strip = GetTabStrip(browser());
      tab_strip->tab_at(index)->SetDataForTesting(std::move(data));
    }
  }

 private:
  base::test::ScopedFeatureList scoped_feature_list_;
};

#if defined(USE_AURA)

// Verify that the hover card is not visible when any key is pressed.
// Because this test depends on Aura event handling, it is not performed on Mac.
IN_PROC_BROWSER_TEST_F(TabHoverCardInteractiveUiTest,
                       HoverCardHidesOnAnyKeyPressInSameWindow) {
  RunTestSequence(
      InstrumentTab(kFirstTabContents, 0),
      NavigateWebContents(kFirstTabContents, chrome::ChromeUINewTabURLAsGURL()),
      HoverTabAt(0), CheckHovercardIsOpen(),
      Check(base::BindLambdaForTesting([=, this]() {
        return ui_test_utils::SendKeyPressSync(browser(), ui::VKEY_DOWN, false,
                                               false, false, false);
      })),
      CheckHovercardIsClosed());
}

#endif

IN_PROC_BROWSER_TEST_F(TabHoverCardInteractiveUiTest,
                       HoverCardHidesOnMouseExit) {
  RunTestSequence(
      InstrumentTab(kFirstTabContents, 0),
      NavigateWebContents(kFirstTabContents, chrome::ChromeUINewTabURLAsGURL()),
      HoverTabAt(0), CheckHovercardIsOpen(), UnhoverTarget(),
      CheckHovercardIsClosed());
}

IN_PROC_BROWSER_TEST_F(TabHoverCardInteractiveUiTest,
                       HoverCardDomainLabelHiddenWhenEmpty) {
  RunTestSequence(
      InstrumentTab(kFirstTabContents, 0),
      NavigateWebContents(kFirstTabContents, chrome::ChromeUINewTabURLAsGURL()),
      HoverTabAt(0),
      WaitForShow(TabHoverCardBubbleView::kHoverCardBubbleElementId),
      WaitForHide(TabHoverCardBubbleView::kHoverCardDomainLabelElementId),
      UnhoverTarget(), CheckHovercardIsClosed());
}

IN_PROC_BROWSER_TEST_F(TabHoverCardInteractiveUiTest,
                       HoverCardShownOnTabFocus) {
  RunTestSequence(InstrumentTab(kFirstTabContents, 0), FocusTabAt(0),
                  CheckHovercardIsOpen());
}

IN_PROC_BROWSER_TEST_F(TabHoverCardInteractiveUiTest,
                       HoverCardVisibleOnTabCloseButtonFocusAfterTabFocus) {
  RunTestSequence(InstrumentTab(kFirstTabContents, 0), FocusTabAt(0),
                  CheckHovercardIsOpen());
}

// Verify hover card is visible when tab is focused and a key is pressed.
IN_PROC_BROWSER_TEST_F(TabHoverCardInteractiveUiTest,
                       WidgetVisibleOnKeyPressAfterTabFocus) {
  RunTestSequence(InstrumentTab(kFirstTabContents, 0), FocusTabAt(0),
                  CheckHovercardIsOpen());
}

// Verify hover card thumbnail is not visible on active tabs.
IN_PROC_BROWSER_TEST_F(TabHoverCardInteractiveUiTest,
                       ThumbnailNotVisibileOnActiveTabs) {
  RunTestSequence(InstrumentTab(kFirstTabContents, 0), FocusTabAt(0),
                  CheckHovercardIsOpen(),
                  CheckView(TabHoverCardBubbleView::kHoverCardBubbleElementId,
                            [](TabHoverCardBubbleView* bubble) {
                              views::View* const thumbnail =
                                  bubble->GetThumbnailViewForTesting();
                              return !thumbnail || !thumbnail->GetVisible();
                            }));
}

// Verify hover card is not visible when tab is focused and the mouse is
// pressed.
IN_PROC_BROWSER_TEST_F(TabHoverCardInteractiveUiTest,
                       WidgetNotVisibleOnMousePressAfterTabFocus) {
  RunTestSequence(InstrumentTab(kFirstTabContents, 0), FocusTabAt(0),
                  CheckHovercardIsOpen(), HoverTabAt(0), ClickMouse(),
                  CheckHovercardIsClosed());
}

IN_PROC_BROWSER_TEST_F(TabHoverCardInteractiveUiTest,
                       WidgetNotVisibleOnMousePressAfterHover) {
  RunTestSequence(
      InstrumentTab(kFirstTabContents, 0),
      NavigateWebContents(kFirstTabContents, chrome::ChromeUINewTabURLAsGURL()),
      HoverTabAt(0), CheckHovercardIsOpen(), SelectTab(kTabStripElementId, 0),
      CheckHovercardIsClosed());
}

IN_PROC_BROWSER_TEST_F(TabHoverCardInteractiveUiTest,
                       HoverCardVisibleOnTabFocusFromKeyboardAccelerator) {
  RunTestSequence(
      InstrumentTab(kFirstTabContents, 0),
      AddInstrumentedTab(kSecondTabContents, GURL(url::kAboutBlankURL)),
      FocusTabAt(0), CheckHovercardIsOpen());
}

IN_PROC_BROWSER_TEST_F(TabHoverCardInteractiveUiTest,
                       InactiveWindowStaysInactiveOnHover) {
  auto focused_tab_strip_model_override =
      resource_coordinator::GetTabLifecycleUnitSource()
          ->SetFocusedTabStripModelForTesting(nullptr);

  // Open a second browser window.
  ui_test_utils::NavigateToURLWithDisposition(
      browser(), GURL(url::kAboutBlankURL), WindowOpenDisposition::NEW_WINDOW,
      ui_test_utils::BROWSER_TEST_WAIT_FOR_BROWSER);
  ASSERT_EQ(2u, GlobalBrowserCollection::GetInstance()->GetSize());

  // Choose one browser to be active; the other to be inactive.
  BrowserWindowInterface* const active_window =
      GetLastActiveBrowserWindowInterfaceWithAnyProfile();
  BrowserWindowInterface* const inactive_window =
      ui_test_utils::GetBrowserNotInSet({active_window});

  // Activate the active browser and wait for the inactive browser to be
  // inactive.
  BrowserView::GetBrowserViewForBrowser(active_window)->Activate();
  views::test::WaitForWidgetActive(
      BrowserView::GetBrowserViewForBrowser(inactive_window)->GetWidget(),
      false);
  ASSERT_FALSE(
      BrowserView::GetBrowserViewForBrowser(inactive_window)->IsActive());

  // Simulate hovering the inactive tabstrip and wait for the hover card to
  // appear. The inactive browser should remain inactive.
  SimulateHoverTab(inactive_window, 0);
  ASSERT_FALSE(
      BrowserView::GetBrowserViewForBrowser(inactive_window)->IsActive());
}

IN_PROC_BROWSER_TEST_F(TabHoverCardInteractiveUiTest,
                       UpdatesHoverCardOnHoverDifferentTab) {
  ASSERT_TRUE(
      AddTabAtIndex(1, GURL(url::kAboutBlankURL), ui::PAGE_TRANSITION_TYPED));
  SetTabData(1, MakeTabData());

  SimulateHoverTab(browser(), 0);

  auto* const hover_card = SimulateHoverTab(browser(), 1);
  EXPECT_EQ(kTabTitle, hover_card->GetTitleViewForTesting()->GetText());
  EXPECT_EQ(kTabDomain, hover_card->GetDomainViewForTesting()->GetText());
  views::View* expected_anchor =
      base::FeatureList::IsEnabled(tabs::kTabStripUnification)
          ? BrowserView::GetBrowserViewForBrowser(browser())
                ->tab_strip_view()
                ->GetTabAnchorView(browser()
                                       ->GetTabStripModel()
                                       ->GetTabAtIndex(1)
                                       ->GetHandle())
          : static_cast<views::View*>(GetTabStrip(browser())->tab_at(1));
  EXPECT_EQ(expected_anchor, hover_card->GetAnchorView());
}

IN_PROC_BROWSER_TEST_F(TabHoverCardInteractiveUiTest,
                       HoverCardDoesNotHaveFooterView) {
  ASSERT_TRUE(
      AddTabAtIndex(1, GURL(url::kAboutBlankURL), ui::PAGE_TRANSITION_TYPED));
  tabs::TabData tab_data = tabs::TabData();
  tab_data.title = kTabTitle;
  tab_data.last_committed_url = GURL(kTabUrl);
  SetTabData(1, tab_data);

  auto* const hover_card = SimulateHoverTab(browser(), 1);
  EXPECT_FALSE(hover_card->GetFooterViewForTesting()->GetVisible());
}

using TabHoverCardBubbleViewMetricsTest = TabHoverCardInteractiveUiTest;

IN_PROC_BROWSER_TEST_F(TabHoverCardBubbleViewMetricsTest,
                       HoverCardsSeenRatioMetric) {
  RunTestSequence(
      InstrumentTab(kFirstTabContents, 0),
      AddInstrumentedTab(kSecondTabContents, GURL(url::kAboutBlankURL)),
      AddInstrumentedTab(kThirdTabContents, GURL(url::kAboutBlankURL)),
      HoverTabAt(0),
      Check([this]() { return GetHoverCardsSeenCount(browser()) == 1; }),
      HoverTabAt(1),
      Check([this]() { return GetHoverCardsSeenCount(browser()) == 2; }),
      SelectTab(kTabStripRegionElementId, 1),
      Check([this]() { return GetHoverCardsSeenCount(browser()) == 0; }));
}

// Tests for tabs showing interstitials to check whether the URL in the hover
// card is displayed or hidden as appropriate.
class TabHoverCardBubbleViewInterstitialBrowserTest
    : public TabHoverCardInteractiveUiTest {
 public:
  ~TabHoverCardBubbleViewInterstitialBrowserTest() override = default;

  void SetUpOnMainThread() override {
    host_resolver()->AddRule("*", "127.0.0.1");

    https_server_mismatched_ = std::make_unique<net::EmbeddedTestServer>(
        net::EmbeddedTestServer::TYPE_HTTPS);
    https_server_mismatched_->SetSSLConfig(
        net::EmbeddedTestServer::CERT_MISMATCHED_NAME);
    https_server_mismatched_->AddDefaultHandlers(GetChromeTestDataDir());

    TabHoverCardInteractiveUiTest::SetUpOnMainThread();
    lookalikes::InitializeSafetyTipConfig();
  }

  net::EmbeddedTestServer* https_server_mismatched() {
    return https_server_mismatched_.get();
  }

 private:
  std::unique_ptr<net::EmbeddedTestServer> https_server_mismatched_;
};

// Verify that the domain field of tab's hover card is empty if the tab is
// showing a lookalike interstitial is ("Did you mean google.com?").
IN_PROC_BROWSER_TEST_F(TabHoverCardBubbleViewInterstitialBrowserTest,
                       LookalikeInterstitial_ShouldHideHoverCardUrl) {
  //  Navigate the tab to a lookalike URL and check the hover card. The domain
  //  field must be empty.
  static constexpr char kLookalikeDomain[] = "googlé.com";
  static constexpr char kUrlPath[] = "/empty.html";
  const GURL url = embedded_test_server()->GetURL(kLookalikeDomain, kUrlPath);
  ASSERT_TRUE(ui_test_utils::NavigateToURL(browser(), url));

  content::WebContents* const tab =
      browser()->GetTabStripModel()->GetActiveWebContents();
  ASSERT_TRUE(chrome_browser_interstitials::IsShowingInterstitial(tab));

  // Open another tab.
  chrome::NewTab(browser(), NewTabTypes::kNoUserAction);
  auto* const hover_card = SimulateHoverTab(browser(), 0);

  EXPECT_TRUE(hover_card->GetDomainViewForTesting()->GetText().empty());
  EXPECT_EQ(1, GetHoverCardsSeenCount(browser()));
}

// Verify that the domain field of tab's hover card is not empty on other types
// of interstitials (here, SSL).
IN_PROC_BROWSER_TEST_F(TabHoverCardBubbleViewInterstitialBrowserTest,
                       SSLInterstitial_ShouldShowHoverCardUrl) {
  ASSERT_TRUE(https_server_mismatched()->Start());
  // Navigate the tab to an SSL error.
  static constexpr char kBadSSLDomain[] = "site.test";
  static constexpr char kUrlPath[] = "/empty.html";
  const GURL url = https_server_mismatched()->GetURL(kBadSSLDomain, kUrlPath);
  ASSERT_TRUE(ui_test_utils::NavigateToURL(browser(), url));

  content::WebContents* const tab =
      browser()->GetTabStripModel()->GetActiveWebContents();
  ASSERT_TRUE(chrome_browser_interstitials::IsShowingInterstitial(tab));

  // Open another tab.
  chrome::NewTab(browser(), NewTabTypes::kNoUserAction);
  auto* const hover_card = SimulateHoverTab(browser(), 0);

  EXPECT_EQ(base::UTF8ToUTF16(net::GetHostAndPort(url)),
            hover_card->GetDomainViewForTesting()->GetText());
  EXPECT_EQ(1, GetHoverCardsSeenCount(browser()));
}

class TabHoverCardFadeFooterInteractiveUiTest
    : public TabHoverCardInteractiveUiTest {
 public:
  void SetUpOnMainThread() override {
    TabHoverCardInteractiveUiTest::SetUpOnMainThread();
    g_browser_process->local_state()->SetBoolean(
        prefs::kHoverCardMemoryUsageEnabled, true);
  }

  FadeAlertFooterRow* GetPrimaryAlertRowFromHoverCard(
      TabHoverCardBubbleView* bubble) {
    return bubble->GetFooterViewForTesting()
        ->GetAlertRowForTesting()
        ->GetPrimaryViewForTesting();
  }

  FadePerformanceFooterRow* GetPrimaryPerformanceRowFromHoverCard(
      TabHoverCardBubbleView* bubble) {
    return bubble->GetFooterViewForTesting()
        ->GetPerformanceRowForTesting()
        ->GetPrimaryViewForTesting();
  }

  FadeCollaborationMessagingFooterRow*
  GetPrimaryCollaborationMessagingRowFromHoverCard(
      TabHoverCardBubbleView* bubble) {
    return bubble->GetFooterViewForTesting()
        ->GetCollaborationMessagingRowForTesting()
        ->GetPrimaryViewForTesting();
  }

  auto CheckAlertRowLabel(std::u16string expected_text, bool has_position) {
    return Steps(
        WaitForShow(TabHoverCardBubbleView::kHoverCardBubbleElementId),
        CheckView(
            TabHoverCardBubbleView::kHoverCardBubbleElementId,
            [=, this](TabHoverCardBubbleView* bubble) {
              views::Label* const alert_label =
                  GetPrimaryAlertRowFromHoverCard(bubble)->footer_label();
              return alert_label->GetText().find(expected_text) !=
                     std::string::npos;
            },
            has_position));
  }

  auto ForceTabDataRefreshMemoryMetrics() {
    return Do([]() {
      TabResourceUsageRefreshWaiter waiter;
      waiter.Wait();
    });
  }
};

// Mocks the hover card footer is playing audio and verifies that
// the correct string is shown for the corresponding tab alert.
IN_PROC_BROWSER_TEST_F(TabHoverCardFadeFooterInteractiveUiTest,
                       HoverCardFooterUpdatesTabAlertStatus) {
  ASSERT_TRUE(
      AddTabAtIndex(1, GURL(url::kAboutBlankURL), ui::PAGE_TRANSITION_TYPED));
  SetTabData(1, MakeTabData());

  FadeAlertFooterRow* const alert_row =
      GetPrimaryAlertRowFromHoverCard(SimulateHoverTab(browser(), 1));
  EXPECT_EQ(
      l10n_util::GetStringUTF16(IDS_TOOLTIP_TAB_ALERT_STATE_AUDIO_PLAYING),
      alert_row->footer_label()->GetText());
  EXPECT_FALSE(alert_row->icon()->GetImageModel().IsEmpty());

  // Hover card footer should update when we hover over another tab that is
  // not playing audio
  SimulateHoverTab(browser(), 0);
  EXPECT_TRUE(alert_row->footer_label()->GetText().empty());
  EXPECT_TRUE(alert_row->icon()->GetImageModel().IsEmpty());
}

class TabHoverCardFadeFooterWithDiscardInteractiveUiTest
    : public TabHoverCardFadeFooterInteractiveUiTest,
      public ::testing::WithParamInterface<bool> {
 public:
  TabHoverCardFadeFooterWithDiscardInteractiveUiTest() {
    scoped_feature_list_.InitWithFeatureState(features::kWebContentsDiscard,
                                              GetParam());
  }

 private:
  base::test::ScopedFeatureList scoped_feature_list_;
};

// Mocks that a tab is discarded and verifies that the correct string
// for a discarded tab and discarded tab with memory usage is displayed
// on the hover card
IN_PROC_BROWSER_TEST_P(TabHoverCardFadeFooterWithDiscardInteractiveUiTest,
                       HoverCardFooterShowsDiscardStatus) {
  ASSERT_TRUE(
      AddTabAtIndex(1, GURL(url::kAboutBlankURL), ui::PAGE_TRANSITION_TYPED));
  tabs::TabData tab_data = MakeTabData();
  tab_data.should_show_discard_status = true;
  SetTabData(1, tab_data);

  FadeAlertFooterRow* const alert_row =
      GetPrimaryAlertRowFromHoverCard(SimulateHoverTab(browser(), 1));
  EXPECT_EQ(l10n_util::GetStringUTF16(IDS_HOVERCARD_INACTIVE_TAB),
            alert_row->footer_label()->GetText());
  EXPECT_FALSE(alert_row->icon()->GetImageModel().IsEmpty());

  // Clear the memory usage data from tab 0 if it was set, otherwise the
  // performance row won't be empty.
  tabs::TabData tab_0_data;
  tab_0_data.tab_resource_usage = nullptr;
  SetTabData(0, tab_0_data);

  // Hover card footer should update when we hover over another tab that is
  // not discarded
  SimulateHoverTab(browser(), 0);
  EXPECT_TRUE(alert_row->footer_label()->GetText().empty());
  EXPECT_TRUE(alert_row->icon()->GetImageModel().IsEmpty());

  // Show discard status with memory savings
  tab_data.discarded_memory_savings = base::ByteSize(1000);
  SetTabData(1, tab_data);
  SimulateHoverTab(browser(), 1);
  EXPECT_EQ(
      l10n_util::FormatString(
          l10n_util::GetStringUTF16(IDS_HOVERCARD_INACTIVE_TAB_MEMORY_SAVINGS),
          {ui::FormatBytes(tab_data.discarded_memory_savings.value())},
          nullptr),
      alert_row->footer_label()->GetText());
}

// With memory usage in hovercards pref set to enabled, mocks a tab with normal
// memory usage and verifies that the string for normal memory usage is shown on
// the hover card. Also tests that the hover card updates this string and use
// the high memory usage string instead when the tab uses memory above the
// threshold.
IN_PROC_BROWSER_TEST_F(TabHoverCardFadeFooterInteractiveUiTest,
                       HoverCardFooterMemoryUsagePrefEnabled) {
  g_browser_process->local_state()->SetBoolean(
      prefs::kHoverCardMemoryUsageEnabled, true);
  ASSERT_TRUE(
      AddTabAtIndex(1, GURL(url::kAboutBlankURL), ui::PAGE_TRANSITION_TYPED));

  base::ByteSize memory_usage = base::ByteSize(1000);
  auto* const tab_resource_usage_tab_helper = GetResourceUsageAt(1);
  tab_resource_usage_tab_helper->SetMemoryUsage(memory_usage);

  // Show memory usage without savings
  FadePerformanceFooterRow* const performance_row =
      GetPrimaryPerformanceRowFromHoverCard(SimulateHoverTab(browser(), 1));
  EXPECT_EQ(l10n_util::FormatString(
                l10n_util::GetStringUTF16(IDS_HOVERCARD_TAB_MEMORY_USAGE),
                {ui::FormatBytes(memory_usage)}, nullptr),
            performance_row->footer_label()->GetText());
  EXPECT_FALSE(performance_row->icon()->GetImageModel().IsEmpty());

  // Hover card updates and shows high memory usage when card is still open.
  memory_usage =
      TabResourceUsage::kHighMemoryUsageThreshold + base::ByteSize(100);
  tab_resource_usage_tab_helper->SetMemoryUsage(memory_usage);
  GetHoverCardController(browser())->OnTabResourceMetricsRefreshed();
  EXPECT_EQ(l10n_util::FormatString(
                l10n_util::GetStringUTF16(IDS_HOVERCARD_TAB_HIGH_MEMORY_USAGE),
                {ui::FormatBytes(memory_usage)}, nullptr),
            performance_row->footer_label()->GetText());
}

// With memory usage in hovercards pref set to disabled, mocks a tab with normal
// memory usage and verifies that the string for normal memory usage is not
// shown on the hover card. Also tests that the hover card does show the high
// memory usage string when the tab uses memory above the threshold.
IN_PROC_BROWSER_TEST_F(TabHoverCardFadeFooterInteractiveUiTest,
                       HoverCardFooterMemoryUsagePrefDisabled) {
  g_browser_process->local_state()->SetBoolean(
      prefs::kHoverCardMemoryUsageEnabled, false);
  ASSERT_TRUE(
      AddTabAtIndex(1, GURL(url::kAboutBlankURL), ui::PAGE_TRANSITION_TYPED));

  base::ByteSize memory_usage = base::ByteSize(1000);
  auto* const tab_resource_usage_tab_helper = GetResourceUsageAt(1);
  tab_resource_usage_tab_helper->SetMemoryUsage(memory_usage);

  // Don't show memory usage
  FadePerformanceFooterRow* const performance_row =
      GetPrimaryPerformanceRowFromHoverCard(SimulateHoverTab(browser(), 1));
  EXPECT_TRUE(performance_row->footer_label()->GetText().empty());
  EXPECT_TRUE(performance_row->icon()->GetImageModel().IsEmpty());

  // Hover card updates and shows high memory usage when card is still open
  memory_usage =
      TabResourceUsage::kHighMemoryUsageThreshold + base::ByteSize(100);
  tab_resource_usage_tab_helper->SetMemoryUsage(memory_usage);
  GetHoverCardController(browser())->OnTabResourceMetricsRefreshed();
  EXPECT_EQ(l10n_util::FormatString(
                l10n_util::GetStringUTF16(IDS_HOVERCARD_TAB_HIGH_MEMORY_USAGE),
                {ui::FormatBytes(memory_usage)}, nullptr),
            performance_row->footer_label()->GetText());
}

IN_PROC_BROWSER_TEST_F(TabHoverCardFadeFooterInteractiveUiTest,
                       ActiveMemoryUsageHidesOnDiscard) {
  const base::ByteSize memory_usage = base::ByteSize(1);
  GetResourceUsageAt(0)->SetMemoryUsage(memory_usage);

  RunTestSequence(InstrumentTab(kFirstTabContents, 0),
                  NavigateWebContents(kFirstTabContents, GetURL("a.com")),
                  AddInstrumentedTab(kSecondTabContents, GetURL("b.com")),
                  ForceTabDataRefreshMemoryMetrics(), TryDiscardTab(0),
                  HoverTabAt(0),
                  WaitForShow(FooterView::kHoverCardFooterElementId),
                  CheckAlertRowLabel(u"Inactive tab", true),
                  CheckView(
                      TabHoverCardBubbleView::kHoverCardBubbleElementId,
                      [=, this](TabHoverCardBubbleView* bubble) {
                        return GetPrimaryPerformanceRowFromHoverCard(bubble)
                            ->footer_label()
                            ->GetText()
                            .empty();
                      },
                      true));
}

// The discarded status in the hover card footer should disappear after a
// discarded tab is reloaded
IN_PROC_BROWSER_TEST_P(TabHoverCardFadeFooterWithDiscardInteractiveUiTest,
                       DiscardStatusHidesOnReload) {
  RunTestSequence(
      InstrumentTab(kFirstTabContents, 0),
      NavigateWebContents(kFirstTabContents, GetURL("a.com")),
      AddInstrumentedTab(kSecondTabContents, GetURL("b.com")), UnhoverTarget(),
      ForceTabDataRefreshMemoryMetrics(), HoverTabAt(0), CheckHovercardIsOpen(),
      CheckAlertRowLabel(u"Inactive tab", false), UnhoverTarget(),
      CheckHovercardIsClosed(),
      // Check that the discarded tab should update its contents to show discard
      // status
      TryDiscardTab(0), HoverTabAt(0),
      WaitForShow(FooterView::kHoverCardFooterElementId),
      CheckAlertRowLabel(u"Inactive tab", true), UnhoverTarget(),
      CheckHovercardIsClosed(),
      // Ensure that hover card shows footer only for the discarded tab
      HoverTabAt(1), CheckHovercardIsOpen(),
      CheckAlertRowLabel(u"Inactive tab", false),
      // Select discarded tab
      UnhoverTarget(), CheckHovercardIsClosed(),
      SelectTab(kTabStripElementId, 0),
      WaitForWebContentsReady(kFirstTabContents), HoverTabAt(0),
      CheckHovercardIsOpen(), CheckAlertRowLabel(u"Inactive tab", false));
}

// The hover card should stop showing memory usage data after navigating to
// another site since the data is now out of date
IN_PROC_BROWSER_TEST_F(TabHoverCardFadeFooterInteractiveUiTest,
                       MemoryUpdatesOnNavigation) {
  const base::ByteSize memory_usage = base::ByteSize(1);
  GetResourceUsageAt(0)->SetMemoryUsage(memory_usage);

  RunTestSequence(
      InstrumentTab(kFirstTabContents, 0), UnhoverTarget(), HoverTabAt(0),
      CheckHovercardIsOpen(),
      WaitForShow(FooterView::kHoverCardFooterElementId), UnhoverTarget(),
      CheckHovercardIsClosed(),
      NavigateWebContents(kFirstTabContents, GetURL("a.com")), HoverTabAt(0),
      CheckHovercardIsOpen(),
      CheckView(
          TabHoverCardBubbleView::kHoverCardBubbleElementId,
          [=, this](TabHoverCardBubbleView* bubble) {
            views::Label* const performance_label =
                GetPrimaryPerformanceRowFromHoverCard(bubble)->footer_label();
            return performance_label->GetText().find(l10n_util::GetStringFUTF16(
                       IDS_HOVERCARD_TAB_MEMORY_USAGE,
                       ui::FormatBytes(memory_usage))) != std::string::npos;
          },
          false));
}

// The hover card should display tab memory usage in the footer. When the user
// hovers over a tab without memory data available, the footer should update
// accordingly and stop showing memory usage
IN_PROC_BROWSER_TEST_F(TabHoverCardFadeFooterInteractiveUiTest,
                       FooterHidesOnTabWithoutMemoryUsage) {
  ASSERT_TRUE(
      AddTabAtIndex(1, GURL(url::kAboutBlankURL), ui::PAGE_TRANSITION_TYPED));

  GetResourceUsageAt(0)->SetMemoryUsage(base::ByteSize(1000));

  // Footer should show when hovering over tab with memory usage
  views::View* const footer_view =
      SimulateHoverTab(browser(), 0)->GetFooterViewForTesting();
  EXPECT_TRUE(footer_view->GetVisible());

  // Hover over a tab without memory usage
  GetResourceUsageAt(1)->SetMemoryUsage(base::ByteSize(0));
  SimulateHoverTab(browser(), 1);

  // Footer should no longer be visible because there is no memory data
  EXPECT_FALSE(footer_view->GetVisible());
}

IN_PROC_BROWSER_TEST_F(TabHoverCardFadeFooterInteractiveUiTest,
                       BackgroundTabHoverCardContentsHaveCorrectDimensions) {
  tabs::TabData data;
  data.alert_state = {tabs::TabAlert::kAudioPlaying};
  RunTestSequence(
      InstrumentTab(kFirstTabContents, 0),
      AddInstrumentedTab(kSecondTabContents, GURL(url::kAboutBlankURL)),
      SelectTab(kTabStripRegionElementId, 0),
      Do([this, data]() { SetTabData(1, data); }), HoverTabAt(1),
      CheckHovercardIsOpen(),
      CheckView(TabHoverCardBubbleView::kHoverCardBubbleElementId,
                [](TabHoverCardBubbleView* hover_card) {
                  gfx::Size hover_card_size =
                      hover_card->GetTabCardViewForTesting()->size();
                  for (views::View* child :
                       hover_card->GetTabCardViewForTesting()->children()) {
                    if (child->GetVisible()) {
                      if (child->size().width() > hover_card_size.width()) {
                        return false;
                      }
                    }
                  }
                  return true;
                }));
}

// Mocks that a tab has collaboration messaging and verifies that the correct
// string is displayed on the hover card.
IN_PROC_BROWSER_TEST_F(TabHoverCardFadeFooterInteractiveUiTest,
                       HoverCardFooterShowsCollaborationMessaging) {
  ASSERT_TRUE(
      AddTabAtIndex(1, GURL(url::kAboutBlankURL), ui::PAGE_TRANSITION_TYPED));
  tabs::TabData tab_data = MakeTabData();

  // Clear alert state. Alerts take precedence over all other footers.
  tab_data.alert_state = {};

  tab_groups::CollaborationMessagingTabData* const data =
      tab_groups::CollaborationMessagingTabData::From(
          browser()->GetTabStripModel()->GetTabAtIndex(1));

  tab_data.collaboration_messaging = data->GetWeakPtr();

  // Do not make a network request for the user's avatar.
  data->set_mocked_avatar_for_testing(gfx::Image());

  // Create a mock PersistentMessage
  // Show collaboration messaging status with TAB_ADDED event.
  std::string given_name = "User";
  std::string avatar_url = "https://google.com/chrome/1";
  data->SetMessage(
      CreateMessage(given_name, avatar_url,
                    collaboration::messaging::CollaborationEvent::TAB_ADDED));

  SetTabData(1, tab_data);
  FadeCollaborationMessagingFooterRow* const collaboration_messaging_row =
      GetPrimaryCollaborationMessagingRowFromHoverCard(
          SimulateHoverTab(browser(), 1));
  EXPECT_EQ(u"User added this tab",
            collaboration_messaging_row->footer_label()->GetText());
  EXPECT_FALSE(collaboration_messaging_row->icon()->GetImageModel().IsEmpty());

  // Hover card footer should update when we hover over another tab that is
  // not discarded
  SimulateHoverTab(browser(), 0);
  EXPECT_TRUE(collaboration_messaging_row->footer_label()->GetText().empty());
  EXPECT_TRUE(collaboration_messaging_row->icon()->GetImageModel().IsEmpty());

  // Reset tab data by setting intermediate object. Without this, the new
  // tab_data is ignored because it is the same object.
  SetTabData(1, MakeTabData());

  // Change username and action to show collaboration messaging with TAB_UPDATED
  // event.
  std::string given_name2 = "Another User";
  std::string avatar_url2 = "https://google.com/chrome/2";
  data->SetMessage(
      CreateMessage(given_name2, avatar_url2,
                    collaboration::messaging::CollaborationEvent::TAB_UPDATED));

  SetTabData(1, tab_data);
  SimulateHoverTab(browser(), 1);
  EXPECT_EQ(u"Another User changed this tab",
            collaboration_messaging_row->footer_label()->GetText());
  EXPECT_FALSE(collaboration_messaging_row->icon()->GetImageModel().IsEmpty());
}

#if BUILDFLAG(IS_CHROMEOS)
class TabHoverCardSystemWebAppTest : public InteractiveBrowserTest {
 public:
  TabHoverCardSystemWebAppTest()
      : test_system_web_app_installation_(
            ash::TestSystemWebAppInstallation::SetUpTabbedMultiWindowApp()) {}

  void SetUpOnMainThread() override {
    InteractiveBrowserTest::SetUpOnMainThread();
    Tab::SetShowHoverCardOnMouseHoverForTesting(true);
  }

 protected:
  std::unique_ptr<ash::TestSystemWebAppInstallation>
      test_system_web_app_installation_;
};

IN_PROC_BROWSER_TEST_F(TabHoverCardSystemWebAppTest,
                       HideDomainNameFromHoverCard) {
  test_system_web_app_installation_->WaitForAppInstall();
  auto* const app_browser = web_app::LaunchWebAppBrowser(
      browser()->GetProfile(), test_system_web_app_installation_->GetAppId());
  const char kTabToHover[] = "Tab to hover";

  RunTestSequenceInContext(
      BrowserElements::From(app_browser)->GetContext(),
      WithView(kTabStripElementId,
               [](TabStrip* tab_strip) { tab_strip->StopAnimating(); }),
      NameDescendantViewByType<Tab>(kBrowserViewElementId, kTabToHover, 0),
      MoveMouseTo(kTabToHover),
      WaitForShow(TabHoverCardBubbleView::kHoverCardBubbleElementId),
      EnsureNotPresent(TabHoverCardBubbleView::kHoverCardDomainLabelElementId),
      MoveMouseTo(kNewTabButtonElementId),
      WaitForHide(TabHoverCardBubbleView::kHoverCardBubbleElementId));
}
#endif

// TODO(crbug.com/497970633): Fix flakiness and enable tab group header
// hover card tests.
IN_PROC_BROWSER_TEST_F(TabHoverCardInteractiveUiTest,
                       DISABLED_HoverCardShowsOnGroupHeader) {
  browser()->GetTabStripModel()->AddToNewGroup({0});

  RunTestSequence(
      WaitForShow(kTabGroupHeaderElementId),
      MoveMouseTo(kNewTabButtonElementId),
      MoveMouseTo(kTabGroupHeaderElementId),
      WaitForShow(TabHoverCardBubbleView::kHoverCardBubbleElementId));
}

IN_PROC_BROWSER_TEST_F(TabHoverCardInteractiveUiTest,
                       DISABLED_GroupHoverCardHidesOnMouseExit) {
  browser()->GetTabStripModel()->AddToNewGroup({0});
  RunTestSequence(
      WaitForShow(kTabGroupHeaderElementId),
      MoveMouseTo(kNewTabButtonElementId),
      MoveMouseTo(kTabGroupHeaderElementId),
      WaitForShow(TabHoverCardBubbleView::kHoverCardBubbleElementId),
      MoveMouseTo(kNewTabButtonElementId),
      WaitForHide(TabHoverCardBubbleView::kHoverCardBubbleElementId));
}

IN_PROC_BROWSER_TEST_F(TabHoverCardInteractiveUiTest,
                       DISABLED_HoverCardShownOnGroupHeaderFocus) {
  browser()->GetTabStripModel()->AddToNewGroup({0});
  RunTestSequence(
      WaitForShow(kTabGroupHeaderElementId), FinishTabstripAnimations(),
      FocusElement(kTabGroupHeaderElementId),
      WaitForShow(TabHoverCardBubbleView::kHoverCardBubbleElementId));
}

IN_PROC_BROWSER_TEST_F(TabHoverCardInteractiveUiTest,
                       DISABLED_HoverCardTransitionFromGroupToTab) {
  ASSERT_TRUE(
      AddTabAtIndex(1, GURL(url::kAboutBlankURL), ui::PAGE_TRANSITION_TYPED));

  browser()->GetTabStripModel()->AddToNewGroup({0});

  RunTestSequence(
      WaitForShow(kTabGroupHeaderElementId),
      MoveMouseTo(kNewTabButtonElementId),
      MoveMouseTo(kTabGroupHeaderElementId),
      WaitForShow(TabHoverCardBubbleView::kHoverCardBubbleElementId),
      WaitForShow(TabHoverCardBubbleView::kGroupCardElementId),
      EnsureNotPresent(TabHoverCardBubbleView::kTabCardElementId),
      HoverTabAt(1), WaitForShow(TabHoverCardBubbleView::kTabCardElementId),
      EnsureNotPresent(TabHoverCardBubbleView::kGroupCardElementId));
}

IN_PROC_BROWSER_TEST_F(TabHoverCardInteractiveUiTest, HideHoverCardLock) {
  ASSERT_TRUE(
      AddTabAtIndex(1, GURL(url::kAboutBlankURL), ui::PAGE_TRANSITION_TYPED));
  std::unique_ptr<TabHoverCardController::ScopedHideHoverCardLock> lock;
  RunTestSequence(
      InstrumentTab(kFirstTabContents, 0),
      NavigateWebContents(kFirstTabContents, chrome::ChromeUINewTabURLAsGURL()),
      HoverTabAt(0), CheckHovercardIsOpen(), UnhoverTarget(),
      CheckHovercardIsClosed(), Do([this, &lock]() {
        lock = GetHoverCardController(browser())->GetHoverCardHideLock();
      }),
      HoverTabAt(0), CheckHovercardIsClosed(), Do([&lock]() { lock.reset(); }),
      // After lock is released, we should not expect it to automatically show
      // if it wasn't re-triggered by a new hover event.
      CheckHovercardIsClosed(), HoverTabAt(1), CheckHovercardIsOpen());
}

INSTANTIATE_TEST_SUITE_P(
    ,
    TabHoverCardFadeFooterWithDiscardInteractiveUiTest,
    ::testing::Values(false, true),
    [](const ::testing::TestParamInfo<
        TabHoverCardFadeFooterWithDiscardInteractiveUiTest::ParamType>& info) {
      return info.param ? "RetainedWebContents" : "UnretainedWebContents";
    });
