// Copyright 2020 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/ui/views/overlay/video_overlay_window_views.h"

#include <algorithm>
#include <memory>
#include <utility>

#include "base/i18n/rtl.h"
#include "base/memory/raw_ptr.h"
#include "base/test/mock_callback.h"
#include "base/test/scoped_feature_list.h"
#include "build/build_config.h"
#include "chrome/browser/picture_in_picture/auto_pip_setting_overlay_view.h"
#include "chrome/browser/picture_in_picture/picture_in_picture_occlusion_tracker.h"
#include "chrome/browser/picture_in_picture/picture_in_picture_widget_fade_animator.h"
#include "chrome/browser/picture_in_picture/picture_in_picture_window_manager.h"
#include "chrome/browser/picture_in_picture/scoped_tuck_picture_in_picture.h"
#include "chrome/browser/ui/views/chrome_layout_provider.h"
#include "chrome/browser/ui/views/overlay/back_to_tab_button.h"
#include "chrome/browser/ui/views/overlay/close_image_button.h"
#include "chrome/browser/ui/views/overlay/hang_up_button.h"
#include "chrome/browser/ui/views/overlay/minimize_button.h"
#include "chrome/browser/ui/views/overlay/overlay_window_live_caption_button.h"
#include "chrome/browser/ui/views/overlay/overlay_window_live_caption_dialog.h"
#include "chrome/browser/ui/views/overlay/playback_image_button.h"
#include "chrome/browser/ui/views/overlay/simple_overlay_window_image_button.h"
#include "chrome/browser/ui/views/overlay/toggle_camera_button.h"
#include "chrome/browser/ui/views/overlay/toggle_microphone_button.h"
#include "chrome/browser/ui/views/overlay/toggle_mute_button.h"
#include "chrome/test/base/testing_profile.h"
#include "chrome/test/views/chrome_views_test_base.h"
#include "components/global_media_controls/public/views/media_progress_view.h"
#include "components/live_caption/pref_names.h"
#include "components/prefs/pref_service.h"
#include "components/prefs/testing_pref_service.h"
#include "components/soda/mock_soda_installer.h"
#include "components/vector_icons/vector_icons.h"
#include "components/viz/common/surfaces/surface_id.h"
#include "content/public/browser/overlay_window.h"
#include "content/public/browser/video_picture_in_picture_window_controller.h"
#include "content/public/browser/web_contents.h"
#include "content/public/test/test_web_contents_factory.h"
#include "media/base/media_switches.h"
#include "services/media_session/public/cpp/media_position.h"
#include "testing/gmock/include/gmock/gmock.h"
#include "ui/base/models/image_model.h"
#include "ui/base/ui_base_features.h"
#include "ui/color/color_provider_key.h"
#include "ui/compositor/layer.h"
#include "ui/display/test/test_screen.h"
#include "ui/events/base_event_utils.h"
#include "ui/events/event.h"
#include "ui/events/test/event_generator.h"
#include "ui/gfx/geometry/point.h"
#include "ui/gfx/geometry/vector2d.h"
#include "ui/native_theme/mock_os_settings_provider.h"
#include "ui/views/accessibility/view_accessibility.h"
#include "ui/views/animation/widget_fade_animator.h"
#include "ui/views/controls/button/label_button.h"
#include "ui/views/controls/button/toggle_button.h"
#include "ui/views/controls/combobox/combobox.h"
#include "ui/views/controls/image_view.h"
#include "ui/views/controls/label.h"
#include "ui/views/test/button_test_api.h"
#include "ui/views/test/views_test_base.h"
#include "ui/views/widget/widget_utils.h"

namespace {

constexpr gfx::Size kMinWindowSize(200, 100);

// Reported minimum bubble size for the setting view.  The pip window should be
// larger than this when the setting view is shown.
constexpr gfx::Size kBubbleSize(300, 200);

// Size that's big enough to accommodate a `kBubbleSize`-sized bubble without
// being further adjusted upwards for margin.
constexpr gfx::Size kSizeBigEnoughForBubble(400, 300);

}  // namespace

using testing::_;
using ::testing::Return;

// Mock of AutoPipSettingOverlayView. Used for injection during tests.
class MockOverlayView : public AutoPipSettingOverlayView {
 public:
  explicit MockOverlayView(views::View* anchor_view)
      : AutoPipSettingOverlayView(base::DoNothing(),
                                  GURL{"https://example.com"},
                                  anchor_view,
                                  views::BubbleBorder::Arrow::FLOAT) {}
  MOCK_METHOD(void, ShowBubble, (gfx::NativeView parent), (override));

  void SetWantsEvent(bool wants_event) { wants_event_ = wants_event; }

  bool WantsEvent(const gfx::Point& point_in_screen) override {
    // Consume any event we're given.  The goal is to make sure we're given the
    // opportunity to take an event.
    return wants_event_;
  }

  gfx::Size GetBubbleSize() const override {
    // Return something that's bigger than the minimum.
    return kBubbleSize;
  }

 private:
  bool wants_event_ = false;
};

class TestVideoPictureInPictureWindowController
    : public content::VideoPictureInPictureWindowController {
 public:
  TestVideoPictureInPictureWindowController() = default;

  // PictureInPictureWindowController:
  void Show() override {}
  void FocusInitiator() override {}
  MOCK_METHOD(void, Close, (bool));
  MOCK_METHOD(void, CloseAndFocusInitiator, ());
  MOCK_METHOD(void, OnWindowDestroyed, (bool));
  content::VideoOverlayWindow* GetWindowForTesting() override {
    return nullptr;
  }
  void UpdateLayerBounds() override {}
  bool IsPlayerActive() override { return false; }
  void set_web_contents(content::WebContents* web_contents) {
    web_contents_ = web_contents;
  }
  content::WebContents* GetWebContents() override { return web_contents_; }
  content::WebContents* GetChildWebContents() override { return nullptr; }
  bool TogglePlayPause() override { return false; }
  void Play() override {}
  void Pause() override {}
  void SkipAd() override {}
  void NextTrack() override {}
  void PreviousTrack() override {}
  void NextSlide() override {}
  void PreviousSlide() override {}
  void ToggleMicrophone() override {}
  void ToggleCamera() override {}
  void HangUp() override {}
  void RequestMute(bool mute) override {}
  bool GetMuteStatus() override { return false; }
  MOCK_METHOD(void, SeekTo, (base::TimeDelta time));
  const gfx::Rect& GetSourceBounds() const override { return source_bounds_; }
  void GetMediaImage(
      const media_session::MediaImage& image,
      int minimum_size_px,
      int desired_size_px,
      content::MediaSession::GetMediaImageBitmapCallback callback) override {
    std::move(callback).Run(
        GetMediaImageImpl(image, minimum_size_px, desired_size_px));
  }
  MOCK_METHOD(SkBitmap,
              GetMediaImageImpl,
              (const media_session::MediaImage& image,
               int minimum_size_px,
               int desired_size_px));
  std::optional<gfx::Rect> GetWindowBoundsInScreen() override {
    return std::nullopt;
  }
  std::optional<url::Origin> GetOrigin() override { return std::nullopt; }
  MOCK_METHOD(bool, IsImmersive, (), (const, override));
  void SetOnWindowCreatedNotifyObserversCallback(base::OnceClosure) override {}

 private:
  raw_ptr<content::WebContents> web_contents_;
  gfx::Rect source_bounds_;
};

class VideoOverlayWindowViewsTest : public ChromeViewsTestBase {
 public:
  VideoOverlayWindowViewsTest() = default;
  // ChromeViewsTestBase:
  void SetUp() override {
    enabled_features_.push_back(media::kPictureInPictureOcclusionTracking);
    feature_list_.InitWithFeatures(enabled_features_, disabled_features_);
    display::Screen::SetScreenInstance(&test_screen_);

    // Purposely skip ChromeViewsTestBase::SetUp() as that creates ash::Shell
    // on ChromeOS, which we don't want.
    ViewsTestBase::SetUp();
    test_views_delegate()->set_layout_provider(
        ChromeLayoutProvider::CreateLayoutProvider());
    mock_soda_installer_ = std::make_unique<speech::MockSodaInstaller>();
    mock_soda_installer_->NeverDownloadSodaForTesting();
    // web_contents_ needs to be created after the constructor, so that
    // |feature_list_| can be initialized before other threads check if a
    // feature is enabled.
    web_contents_ = web_contents_factory_.CreateWebContents(&profile_);
    pip_window_controller_.set_web_contents(web_contents_);

#if BUILDFLAG(IS_CHROMEOS)
    test_views_delegate()->set_context(GetContext());
#endif
    test_views_delegate()->set_use_desktop_native_widgets(true);

    // The default work area must be big enough to fit the minimum
    // VideoOverlayWindowViews size.
    SetDisplayWorkArea({0, 0, 1000, 1000});

    overlay_window_ = VideoOverlayWindowViews::Create(&pip_window_controller_);
    overlay_window_->set_overlay_view_cb_for_testing(
        base::BindRepeating(&VideoOverlayWindowViewsTest::GetOverlayViewImpl,
                            base::Unretained(this)));
    overlay_window_->set_meets_user_interaction_for_testing(true);

    // On some platforms, OnNativeWidgetMove is invoked on creation.
    WaitForMove();
    overlay_window_->set_minimum_size_for_testing(kMinWindowSize);

    event_generator_ = std::make_unique<ui::test::EventGenerator>(
        views::GetRootWindow(overlay_window_.get()));
  }

  void TearDown() override {
    overlay_window_.reset();
    ViewsTestBase::TearDown();
    display::Screen::SetScreenInstance(nullptr);
  }

  void SetDisplayWorkArea(const gfx::Rect& work_area) {
    display::Display display = test_screen_.GetPrimaryDisplay();
    display.set_work_area(work_area);
    test_screen_.display_list().UpdateDisplay(display);
  }

  VideoOverlayWindowViews& overlay_window() { return *overlay_window_; }

  content::WebContents* web_contents() { return web_contents_; }

  TestVideoPictureInPictureWindowController& pip_window_controller() {
    return pip_window_controller_;
  }

  MockOverlayView* SetOverlayView() {
    std::unique_ptr<MockOverlayView> mock_overlay_view =
        std::make_unique<MockOverlayView>(
            overlay_window().window_background_view_for_testing());
    overlay_view_ = std::move(mock_overlay_view);
    return overlay_view_.get();
  }

  ui::test::EventGenerator* event_generator() { return event_generator_.get(); }

 protected:
  void WaitForMove() {
    task_environment()->FastForwardBy(
        VideoOverlayWindowViews::kControlHideDelayAfterMove +
        base::Milliseconds(1));
  }

  void WaitForLayout() { task_environment()->FastForwardBy(base::Seconds(1)); }

  void DestroyOverlayWindow() { overlay_window_.reset(); }

  void AddEnabledFeature(base::test::FeatureRef feature) {
    enabled_features_.push_back(feature);
  }

  void AddDisabledFeature(base::test::FeatureRef feature) {
    disabled_features_.push_back(feature);
  }

  void GestureTapOnView(views::View* view) {
    event_generator_->GestureTapAt(view->GetBoundsInScreen().CenterPoint());
  }

  TestingProfile& profile() { return profile_; }

 private:
  std::unique_ptr<AutoPipSettingOverlayView> GetOverlayViewImpl() {
    return std::move(overlay_view_);
  }

  std::unique_ptr<speech::MockSodaInstaller> mock_soda_installer_;
  TestingProfile profile_;
  content::TestWebContentsFactory web_contents_factory_;
  raw_ptr<content::WebContents> web_contents_;
  TestVideoPictureInPictureWindowController pip_window_controller_;

  display::test::TestScreen test_screen_;

  // Overlay view that we'll send to the window.  May be null.
  std::unique_ptr<MockOverlayView> overlay_view_;

  std::unique_ptr<ui::test::EventGenerator> event_generator_;

  std::unique_ptr<VideoOverlayWindowViews> overlay_window_;

  std::vector<base::test::FeatureRef> enabled_features_;

  std::vector<base::test::FeatureRef> disabled_features_;

  base::test::ScopedFeatureList feature_list_;
};

TEST_F(VideoOverlayWindowViewsTest, InitialWindowSize_Square) {
  // Fit the window taking 1/5 (both dimensions) of the work area as the
  // starting size, and applying the size and aspect ratio constraints.
  overlay_window().UpdateNaturalSize({400, 400});
  EXPECT_EQ(gfx::Size(200, 200), overlay_window().GetBounds().size());
  EXPECT_EQ(gfx::Size(200, 200),
            overlay_window().video_layer_for_testing()->size());
}

TEST_F(VideoOverlayWindowViewsTest, InitialWindowSize_Horizontal) {
  // Fit the window taking 1/5 (both dimensions) of the work area as the
  // starting size, and applying the size and aspect ratio constraints.
  overlay_window().UpdateNaturalSize({400, 200});
  EXPECT_EQ(gfx::Size(400, 200), overlay_window().GetBounds().size());
  EXPECT_EQ(gfx::Size(400, 200),
            overlay_window().video_layer_for_testing()->size());
}

TEST_F(VideoOverlayWindowViewsTest, InitialWindowSize_Vertical) {
  // Fit the window taking 1/5 (both dimensions) of the work area as the
  // starting size, and applying the size and aspect ratio constraints.
  overlay_window().UpdateNaturalSize({400, 500});
  EXPECT_EQ(gfx::Size(200, 250), overlay_window().GetBounds().size());
  EXPECT_EQ(gfx::Size(200, 250),
            overlay_window().video_layer_for_testing()->size());
}

TEST_F(VideoOverlayWindowViewsTest, Letterboxing) {
  overlay_window().UpdateNaturalSize({400, 10});

  // Must fit within the minimum height of 146. But with the aspect ratio of
  // 40:1 the width gets exceedingly big and must be limited to the maximum of
  // 800. Thus, letterboxing is unavoidable.
  EXPECT_EQ(gfx::Size(800, 100), overlay_window().GetBounds().size());
  EXPECT_EQ(gfx::Size(800, 20),
            overlay_window().video_layer_for_testing()->size());
}

TEST_F(VideoOverlayWindowViewsTest, Pillarboxing) {
  overlay_window().UpdateNaturalSize({10, 400});

  // Must fit within the minimum width of 260. But with the aspect ratio of
  // 1:40 the height gets exceedingly big and must be limited to the maximum of
  // 800. Thus, pillarboxing is unavoidable.
  EXPECT_EQ(gfx::Size(200, 800), overlay_window().GetBounds().size());
  EXPECT_EQ(gfx::Size(20, 800),
            overlay_window().video_layer_for_testing()->size());
}

TEST_F(VideoOverlayWindowViewsTest, Pillarboxing_Square) {
  overlay_window().UpdateNaturalSize({100, 100});

  // Pillarboxing also occurs on Linux even with the square aspect ratio,
  // because the user is allowed to size the window to the rectangular minimum
  // size.
  overlay_window().SetSize({200, 100});
  EXPECT_EQ(gfx::Size(100, 100),
            overlay_window().video_layer_for_testing()->size());
}

TEST_F(VideoOverlayWindowViewsTest, ApproximateAspectRatio_Horizontal) {
  // "Horizontal" video.
  overlay_window().UpdateNaturalSize({320, 240});

  // The user drags the window resizer horizontally and now the integer window
  // dimensions can't reproduce the video aspect ratio exactly. The video
  // should still fill the entire window area.
  overlay_window().SetSize({320, 240});
  EXPECT_EQ(gfx::Size(320, 240),
            overlay_window().video_layer_for_testing()->size());

  overlay_window().SetSize({321, 241});
  EXPECT_EQ(gfx::Size(321, 241),
            overlay_window().video_layer_for_testing()->size());

  // Wide video.
  overlay_window().UpdateNaturalSize({1600, 900});

  overlay_window().SetSize({444, 250});
  EXPECT_EQ(gfx::Size(444, 250),
            overlay_window().video_layer_for_testing()->size());

  overlay_window().SetSize({445, 250});
  EXPECT_EQ(gfx::Size(445, 250),
            overlay_window().video_layer_for_testing()->size());

  // Very wide video.
  overlay_window().UpdateNaturalSize({400, 100});

  overlay_window().SetSize({478, 120});
  EXPECT_EQ(gfx::Size(478, 120),
            overlay_window().video_layer_for_testing()->size());

  overlay_window().SetSize({481, 120});
  EXPECT_EQ(gfx::Size(481, 120),
            overlay_window().video_layer_for_testing()->size());
}

TEST_F(VideoOverlayWindowViewsTest, ApproximateAspectRatio_Vertical) {
  // "Vertical" video.
  overlay_window().UpdateNaturalSize({240, 320});

  // The user dragged the window resizer vertically and now the integer window
  // dimensions can't reproduce the video aspect ratio exactly. The video
  // should still fill the entire window area.
  overlay_window().SetSize({240, 320});
  EXPECT_EQ(gfx::Size(240, 320),
            overlay_window().video_layer_for_testing()->size());

  overlay_window().SetSize({239, 319});
  EXPECT_EQ(gfx::Size(239, 319),
            overlay_window().video_layer_for_testing()->size());

  // Narrow video.
  overlay_window().UpdateNaturalSize({900, 1600});

  overlay_window().SetSize({250, 444});
  EXPECT_EQ(gfx::Size(250, 444),
            overlay_window().video_layer_for_testing()->size());

  overlay_window().SetSize({250, 445});
  EXPECT_EQ(gfx::Size(250, 445),
            overlay_window().video_layer_for_testing()->size());

  // Very narrow video.
  // NOTE: Window width is bounded by the minimum size.
  overlay_window().UpdateNaturalSize({100, 400});

  overlay_window().SetSize({200, 478});
  EXPECT_EQ(gfx::Size(120, 478),
            overlay_window().video_layer_for_testing()->size());

  overlay_window().SetSize({200, 481});
  EXPECT_EQ(gfx::Size(120, 481),
            overlay_window().video_layer_for_testing()->size());
}

TEST_F(VideoOverlayWindowViewsTest, UpdateMaximumSize) {
  SetDisplayWorkArea({0, 0, 4000, 4000});

  overlay_window().UpdateNaturalSize({480, 320});

  // The initial size is determined by the work area and the video natural size
  // (aspect ratio).
  EXPECT_EQ(gfx::Size(1200, 800), overlay_window().GetBounds().size());
  // The initial maximum size is 80% of the work area.
  EXPECT_EQ(gfx::Size(3200, 3200), overlay_window().GetMaximumSize());

  // If the maximum size increases then we should keep the existing window size.
  SetDisplayWorkArea({0, 0, 8000, 8000});
  EXPECT_EQ(gfx::Size(1200, 800), overlay_window().GetBounds().size());
  EXPECT_EQ(gfx::Size(6400, 6400), overlay_window().GetMaximumSize());

  // If the maximum size decreases then we should shrink to fit.
  SetDisplayWorkArea({0, 0, 1000, 2000});
  EXPECT_EQ(gfx::Size(800, 800), overlay_window().GetBounds().size());
  EXPECT_EQ(gfx::Size(800, 1600), overlay_window().GetMaximumSize());
}

TEST_F(VideoOverlayWindowViewsTest, IgnoreInvalidMaximumSize) {
  ASSERT_EQ(gfx::Size(800, 800), overlay_window().GetMaximumSize());

  SetDisplayWorkArea({0, 0, 0, 0});
  EXPECT_EQ(gfx::Size(800, 800), overlay_window().GetMaximumSize());
}

TEST_F(VideoOverlayWindowViewsTest, UpdateNaturalSizeDoesNotMoveWindow) {
  // Enter PiP.
  overlay_window().UpdateNaturalSize({300, 200});
  overlay_window().ShowInactive();

  // Resize the window and move it toward the top-left corner of the work area.
  // In production, resizing preserves the aspect ratio if possible, so we
  // preserve it here too.
  overlay_window().SetBounds({100, 100, 450, 300});

  // Simulate a new surface layer and a change in the aspect ratio.
  overlay_window().UpdateNaturalSize({400, 200});

  // The window should not move.
  // The window size will be adjusted according to the new aspect ratio, and
  // clamped to 600x300 to fit within the maximum size for the work area of
  // 1000x1000.
  EXPECT_EQ(gfx::Rect(100, 100, 600, 300), overlay_window().GetBounds());
}

// Tests that the OverlayWindowFrameView does not accept events so they can
// propagate to the overlay.
TEST_F(VideoOverlayWindowViewsTest, HitTestFrameView) {
  // Since the FrameView is the only non-custom direct descendent of
  // the NonClientView, we can assume that if the frame does not accept the
  // point but the NonClientView does, then it will be handled by one of the
  // custom overlay views.
  auto point = gfx::Point(50, 50);
  views::NonClientView* non_client_view = overlay_window().non_client_view();
  EXPECT_EQ(non_client_view->frame_view()->HitTestPoint(point), false);
  EXPECT_EQ(non_client_view->HitTestPoint(point), true);
}

// With pillarboxing, the close button doesn't cover the video area. Make sure
// hovering the button doesn't get handled like normal mouse exit events
// causing the controls to hide.
// TODO(http://crbug.com/41482397): Fix and re-enable.
TEST_F(VideoOverlayWindowViewsTest, DISABLED_NoMouseExitWithinWindowBounds) {
  overlay_window().UpdateNaturalSize({10, 400});
  WaitForMove();

  const auto close_button_bounds = overlay_window().GetCloseControlsBounds();
  const auto video_bounds =
      overlay_window().video_layer_for_testing()->bounds();
  ASSERT_FALSE(video_bounds.Contains(close_button_bounds));

  const gfx::Point moved_location(video_bounds.origin() + gfx::Vector2d(5, 5));
  ui::MouseEvent moved_event(ui::EventType::kMouseMoved, moved_location,
                             moved_location, ui::EventTimeForNow(), ui::EF_NONE,
                             ui::EF_NONE);
  overlay_window().OnMouseEvent(&moved_event);
  ASSERT_TRUE(overlay_window().AreControlsVisible());

  const gfx::Point exited_location(close_button_bounds.CenterPoint());
  ui::MouseEvent exited_event(ui::EventType::kMouseExited, exited_location,
                              exited_location, ui::EventTimeForNow(),
                              ui::EF_NONE, ui::EF_NONE);
  overlay_window().OnMouseEvent(&exited_event);
  EXPECT_TRUE(overlay_window().AreControlsVisible());
}

TEST_F(VideoOverlayWindowViewsTest, ShowControlsOnFocus) {
  EXPECT_FALSE(overlay_window().AreControlsVisible());
  overlay_window().OnNativeFocus();
  EXPECT_TRUE(overlay_window().AreControlsVisible());
}

TEST_F(VideoOverlayWindowViewsTest, OnlyPauseOnCloseWhenPauseIsAvailable) {
  views::test::ButtonTestApi close_button_clicker(
      overlay_window().close_button_for_testing());
  ui::MouseEvent dummy_event(ui::EventType::kMousePressed, gfx::Point(0, 0),
                             gfx::Point(0, 0), ui::EventTimeForNow(), 0, 0);

  // When the play/pause controls are visible, closing via the close button
  // should pause the video.
  overlay_window().SetPlayPauseButtonVisibility(true);
  PictureInPictureWindowManager::GetInstance()
      ->set_window_controller_for_testing(&pip_window_controller());
  EXPECT_CALL(pip_window_controller(), Close(true));
  close_button_clicker.NotifyClick(dummy_event);
  testing::Mock::VerifyAndClearExpectations(&pip_window_controller());

  // Same for tapping the close button. Note that the controls must be visible
  // for the tap to work, otherwise the tap will just show the controls.
  overlay_window().ForceControlsVisibleForTesting(true);
  gfx::Point close_button_center =
      overlay_window().GetCloseControlsBounds().CenterPoint();
  ui::GestureEvent tap_event(
      close_button_center.x(), close_button_center.y(), 0,
      base::TimeTicks::Now(),
      ui::GestureEventDetails(ui::EventType::kGestureTap));
  EXPECT_CALL(pip_window_controller(), Close(true));
  overlay_window().OnGestureEvent(&tap_event);
  testing::Mock::VerifyAndClearExpectations(&pip_window_controller());

  // When the play/pause controls are not visible, closing via the close button
  // should not pause the video.
  overlay_window().SetPlayPauseButtonVisibility(false);
  EXPECT_CALL(pip_window_controller(), Close(false));
  close_button_clicker.NotifyClick(dummy_event);
  testing::Mock::VerifyAndClearExpectations(&pip_window_controller());

  // Same for tapping the close button.
  EXPECT_CALL(pip_window_controller(), Close(false));
  overlay_window().OnGestureEvent(&tap_event);
  testing::Mock::VerifyAndClearExpectations(&pip_window_controller());

  PictureInPictureWindowManager::GetInstance()
      ->set_window_controller_for_testing(nullptr);
}

TEST_F(VideoOverlayWindowViewsTest, PauseOnWidgetCloseWhenPauseAvailable) {
  // When the play/pause controls are visible, when the native widget is
  // destroyed we should pause the underlying video.
  overlay_window().SetPlayPauseButtonVisibility(true);
  EXPECT_CALL(pip_window_controller(), OnWindowDestroyed(true));
  overlay_window().CloseNow();
  testing::Mock::VerifyAndClearExpectations(&pip_window_controller());
}

TEST_F(VideoOverlayWindowViewsTest,
       DontPauseOnWidgetCloseWhenPauseNotAvailable) {
  // When the play/pause controls are not visible, when the native widget is
  // destroyed we should not pause the underlying video.
  overlay_window().SetPlayPauseButtonVisibility(false);
  EXPECT_CALL(pip_window_controller(), OnWindowDestroyed(false));
  overlay_window().CloseNow();
  testing::Mock::VerifyAndClearExpectations(&pip_window_controller());
}

TEST_F(VideoOverlayWindowViewsTest, SmallDisplayWorkAreaDoesNotCrash) {
  SetDisplayWorkArea({0, 0, 240, 120});
  overlay_window().UpdateNaturalSize({400, 300});

  // Since the work area would force a max size smaller than the minimum size,
  // the size is fixed at the minimum size.
  EXPECT_EQ(kMinWindowSize, overlay_window().GetBounds().size());
  EXPECT_EQ(kMinWindowSize, overlay_window().GetMaximumSize());

  // The video should still be letterboxed to the correct aspect ratio.
  EXPECT_EQ(gfx::Size(133, 100),
            overlay_window().video_layer_for_testing()->size());
}

// TODO(http://crbug.com/41482397): Fix and re-enable.
TEST_F(VideoOverlayWindowViewsTest, DISABLED_ControlsAreHiddenDuringMove) {
  // Set the initial position.
  overlay_window().SetBounds({0, 0, 100, 100});
  WaitForMove();

  // Make the controls visible.
  overlay_window().UpdateControlsVisibility(true);
  ASSERT_TRUE(overlay_window().AreControlsVisible());

  // Now move the window, this should cause the controls to be hidden.
  overlay_window().SetBounds({50, 0, 100, 100});
  EXPECT_FALSE(overlay_window().AreControlsVisible());

  // Should still be hidden with mouse event.
  overlay_window().UpdateControlsVisibility(true);
  EXPECT_FALSE(overlay_window().AreControlsVisible());

  // After moving, overlay should be visible again because of the previous
  // mouse event.
  WaitForMove();
  EXPECT_TRUE(overlay_window().AreControlsVisible());
}

TEST_F(VideoOverlayWindowViewsTest,
       ControlsAreHiddenDuringMove_MultipleUpdates) {
  overlay_window().SetBounds({0, 0, 100, 100});
  WaitForMove();

  // Move the window.
  overlay_window().SetBounds({50, 0, 100, 100});
  EXPECT_FALSE(overlay_window().AreControlsVisible());

  overlay_window().UpdateControlsVisibility(true);
  overlay_window().UpdateControlsVisibility(false);
  overlay_window().UpdateControlsVisibility(true);
  overlay_window().UpdateControlsVisibility(false);

  // Only the last one should have any effect.
  EXPECT_FALSE(overlay_window().AreControlsVisible());
}

TEST_F(VideoOverlayWindowViewsTest, OverlayViewIsSizedCorrectly) {
  // Set the bound of the window before showing it, to make sure the size
  // propagates to the overlay view.  We use the larger-than-bubble size so that
  // it should be an exact match.  If it were too small, then the overlay window
  // might have to be even larger than we request to fit the bubble.

  // Setting the overlay view before show should be sufficient for it to take
  // effect when shown.
  auto* overlay_view = SetOverlayView();
  overlay_window().ShowInactive();
  // Do this after showing it, else the window will size to a default size,
  // rather than the bounds we request.
  const gfx::Rect bounds(gfx::Point(0, 0), kSizeBigEnoughForBubble);
  overlay_window().UpdateNaturalSize(bounds.size());
  overlay_window().SetBounds(bounds);
  EXPECT_TRUE(overlay_view->GetVisible());
  EXPECT_EQ(overlay_view->bounds(), bounds);
}

TEST_F(VideoOverlayWindowViewsTest, OverlayViewCanBeClicked) {
  // Make sure that the overlay view is z-ordered to get input events.
  auto* overlay_view = SetOverlayView();
  overlay_view->SetWantsEvent(true);

  // Add a button!
  base::MockRepeatingCallback<void(const ui::Event&)> cb;
  auto* button = overlay_view->AddChildView(
      std::make_unique<views::LabelButton>(cb.Get()));
  button->SetBounds(0, 0, 50, 50);

  // Show the window and click the button.
  overlay_window().ShowInactive();
  EXPECT_CALL(cb, Run(_));
  event_generator()->MoveMouseTo(button->GetBoundsInScreen().CenterPoint());
  event_generator()->ClickLeftButton();

  // Clear the callback since `cb` is going away.  Note that `DoNothing()`
  // doesn't work here because type inference fails.
  button->SetCallback(base::BindRepeating([](const ui::Event&) {}));
}

TEST_F(VideoOverlayWindowViewsTest, OverlayWindowBlocksInput) {
  // Make sure that the playback controls don't receive input events while the
  // overlay view is visible.
  auto* overlay_view = SetOverlayView();
  overlay_view->SetWantsEvent(true);
  overlay_window().ShowInactive();

  // When the play/pause controls are visible, closing via the close button
  // should pause the video.
  overlay_window().SetPlayPauseButtonVisibility(true);
  EXPECT_CALL(pip_window_controller(), Close(true)).Times(0);
  event_generator()->MoveMouseTo(
      overlay_window().GetCloseControlsBounds().CenterPoint());
  event_generator()->ClickLeftButton();
}

TEST_F(VideoOverlayWindowViewsTest, OverlayWindowFitsInMinimumSize) {
  auto* overlay_view = SetOverlayView();
  overlay_window().ShowInactive();

  // The window size should be strictly greater than the bubble size so that
  // there's some nonzero margin.
  auto window_min_size = overlay_window().GetMinimumSize();
  auto bubble_min_size = overlay_view->GetBubbleSize();
  EXPECT_GT(window_min_size.width(), bubble_min_size.width());
  EXPECT_GT(window_min_size.height(), bubble_min_size.height());

  // When the overlay view is hidden, the minimum size should return to normal.
  overlay_view->SetVisible(false);
  EXPECT_EQ(overlay_window().GetMinimumSize(), kMinWindowSize);
}

TEST_F(VideoOverlayWindowViewsTest, OverlayWindowStopsBlockingInput) {
  auto* overlay_view = SetOverlayView();
  overlay_window().ShowInactive();

  // Make sure that the overlay window blocks input, when the overlay view does
  // not want events.
  const auto close_controls_center_point =
      overlay_window().GetCloseControlsBounds().CenterPoint();
  overlay_view->SetWantsEvent(false);
  EXPECT_FALSE(overlay_window().ControlsHitTestContainsPoint(
      close_controls_center_point));

  // Make sure that the overlay window stops blocking input, when the overlay
  // view wants event.
  overlay_view->SetWantsEvent(true);
  EXPECT_TRUE(overlay_window().ControlsHitTestContainsPoint(
      close_controls_center_point));
}

TEST_F(VideoOverlayWindowViewsTest, IsTrackedByTheOcclusionObserver) {
  overlay_window().ShowInactive();

  PictureInPictureOcclusionTracker* tracker =
      PictureInPictureWindowManager::GetInstance()->GetOcclusionTracker();

  // Check that the PictureInPictureOcclusionTracker is observing the
  // VideoOverlayWindowViews.
  EXPECT_TRUE(std::ranges::contains(
      tracker->GetPictureInPictureWidgetsForTesting(), &overlay_window()));

  // Check that it's no longer observed when the widget is destroyed.
  DestroyOverlayWindow();
  EXPECT_EQ(0u, tracker->GetPictureInPictureWidgetsForTesting().size());
}

TEST_F(VideoOverlayWindowViewsTest, CanBeTuckedToTheSideOfTheScreen) {
  // Place the window on the left side of the screen.
  SetDisplayWorkArea({0, 0, 2000, 2000});
  overlay_window().SetBounds({{400, 400}, {500, 500}});

  // If we tell it to force tucking, it should tuck to the left side of the
  // screen.
  overlay_window().SetForcedTucking(true);
  overlay_window().FinishTuckAnimationForTesting();
  EXPECT_LT(overlay_window().GetWindowBoundsInScreen().x(), 0);

  // If we tell it to stop tucking, it should be put back in its original
  // position.
  overlay_window().SetForcedTucking(false);
  overlay_window().FinishTuckAnimationForTesting();
  EXPECT_EQ(overlay_window().GetWindowBoundsInScreen().x(), 400);
}

TEST_F(VideoOverlayWindowViewsTest, UntucksWhenReshownIfNecessary) {
  // Place the window on the left side of the screen.
  SetDisplayWorkArea({0, 0, 2000, 2000});
  overlay_window().SetBounds({{400, 400}, {500, 500}});
  overlay_window().ShowInactive();

  // Start tucking via a ScopedTuckPictureInPicture.
  auto scoped_tuck = std::make_unique<ScopedTuckPictureInPicture>();
  overlay_window().FinishTuckAnimationForTesting();
  EXPECT_LT(overlay_window().GetWindowBoundsInScreen().x(), 0);

  // Hide ourselves. This will mean if tucking ends then the
  // PictureInPictureWindowManager won't actually notify us.
  overlay_window().Hide();

  // End tucking.
  scoped_tuck.reset();

  // Show ourselves. We should check with the PictureInPictureWindowManager and
  // realize we should no longer tuck.
  overlay_window().ShowInactive();
  overlay_window().FinishTuckAnimationForTesting();
  EXPECT_EQ(overlay_window().GetWindowBoundsInScreen().x(), 400);
}

TEST_F(VideoOverlayWindowViewsTest, MinimizeButtonClosesWithoutPausing) {
  views::test::ButtonTestApi minimize_button_clicker(
      overlay_window().minimize_button_for_testing());
  ui::MouseEvent dummy_event(ui::EventType::kMousePressed, gfx::Point(0, 0),
                             gfx::Point(0, 0), ui::EventTimeForNow(), 0, 0);

  // Even when play/pause is available, the minimize button should not pause the
  // video.
  overlay_window().SetPlayPauseButtonVisibility(true);
  PictureInPictureWindowManager::GetInstance()
      ->set_window_controller_for_testing(&pip_window_controller());
  EXPECT_CALL(pip_window_controller(), Close(false));
  minimize_button_clicker.NotifyClick(dummy_event);
  testing::Mock::VerifyAndClearExpectations(&pip_window_controller());
}

TEST_F(VideoOverlayWindowViewsTest, ShowsBackToTabImageButton) {
  overlay_window().ForceControlsVisibleForTesting(true);
  OverlayWindowBackToTabButton* back_to_tab_image_button =
      overlay_window().back_to_tab_button_for_testing();
  ASSERT_NE(nullptr, back_to_tab_image_button);
  EXPECT_TRUE(back_to_tab_image_button->IsDrawn());
  views::test::ButtonTestApi button_clicker(back_to_tab_image_button);
  ui::MouseEvent dummy_event(ui::EventType::kMousePressed, gfx::Point(0, 0),
                             gfx::Point(0, 0), ui::EventTimeForNow(), 0, 0);

  PictureInPictureWindowManager::GetInstance()
      ->set_window_controller_for_testing(&pip_window_controller());
  EXPECT_CALL(pip_window_controller(), CloseAndFocusInitiator());
  button_clicker.NotifyClick(dummy_event);
  testing::Mock::VerifyAndClearExpectations(&pip_window_controller());
}

TEST_F(VideoOverlayWindowViewsTest, ProgressBarSeeksVideo) {
  overlay_window().ShowInactive();
  overlay_window().SetPlayPauseButtonVisibility(true);
  overlay_window().ForceControlsVisibleForTesting(true);

  // Move time forward to ensure controls layout is completed.
  WaitForLayout();

  global_media_controls::MediaProgressView* progress_view =
      overlay_window().progress_view_for_testing();
  ASSERT_NE(nullptr, progress_view);
  EXPECT_TRUE(progress_view->IsDrawn());

  // Before dragging the progress bar, the center controls should be visible.
  PlaybackImageButton* play_button =
      overlay_window().play_pause_controls_view_for_testing();
  SimpleOverlayWindowImageButton* replay_10_seconds_button =
      overlay_window().replay_10_seconds_button_for_testing();
  SimpleOverlayWindowImageButton* forward_10_seconds_button =
      overlay_window().forward_10_seconds_button_for_testing();
  ASSERT_NE(nullptr, play_button);
  ASSERT_NE(nullptr, replay_10_seconds_button);
  ASSERT_NE(nullptr, forward_10_seconds_button);
  EXPECT_TRUE(play_button->IsDrawn());
  EXPECT_TRUE(replay_10_seconds_button->IsDrawn());
  EXPECT_TRUE(forward_10_seconds_button->IsDrawn());

  gfx::Point point(progress_view->width() / 2, progress_view->height() / 2);
  ui::MouseEvent pressed_event(ui::EventType::kMousePressed, point, point,
                               ui::EventTimeForNow(), ui::EF_LEFT_MOUSE_BUTTON,
                               ui::EF_LEFT_MOUSE_BUTTON);
  EXPECT_CALL(pip_window_controller(), SeekTo(_));
  progress_view->OnMousePressed(pressed_event);

  // Move time forward to ensure drag delay timer has fired.
  task_environment()->FastForwardBy(base::Seconds(1));
  testing::Mock::VerifyAndClearExpectations(&pip_window_controller());

  // While dragging, the center controls should not be visible.
  EXPECT_FALSE(play_button->IsDrawn());
  EXPECT_FALSE(replay_10_seconds_button->IsDrawn());
  EXPECT_FALSE(forward_10_seconds_button->IsDrawn());

  ui::MouseEvent released_event = ui::MouseEvent(
      ui::EventType::kMouseReleased, point, point, ui::EventTimeForNow(),
      ui::EF_LEFT_MOUSE_BUTTON, ui::EF_LEFT_MOUSE_BUTTON);
  progress_view->OnMouseReleased(released_event);

  // Once the drag ends, the center controls should be visible again.
  EXPECT_TRUE(play_button->IsDrawn());
  EXPECT_TRUE(replay_10_seconds_button->IsDrawn());
  EXPECT_TRUE(forward_10_seconds_button->IsDrawn());
}

TEST_F(VideoOverlayWindowViewsTest, TimestampDisplaysCurrentTime) {
  overlay_window().ForceControlsVisibleForTesting(true);
  media_session::MediaPosition media_position(/*playback_rate=*/0,
                                              /*duration=*/base::Seconds(100),
                                              /*position=*/base::Seconds(42),
                                              /*end_of_media=*/false);
  overlay_window().SetMediaPosition(media_position);

  views::Label* timestamp = overlay_window().timestamp_for_testing();
  ASSERT_NE(nullptr, timestamp);
  EXPECT_TRUE(timestamp->IsDrawn());
  EXPECT_EQ(u"0:42 / 1:40", timestamp->GetText());
}

TEST_F(VideoOverlayWindowViewsTest, ReplayAndForward10SecondsSeekVideo) {
  overlay_window().ForceControlsVisibleForTesting(true);
  media_session::MediaPosition media_position(/*playback_rate=*/0,
                                              /*duration=*/base::Seconds(100),
                                              /*position=*/base::Seconds(42),
                                              /*end_of_media=*/false);
  overlay_window().SetMediaPosition(media_position);
  SimpleOverlayWindowImageButton* replay_10_seconds_button =
      overlay_window().replay_10_seconds_button_for_testing();
  SimpleOverlayWindowImageButton* forward_10_seconds_button =
      overlay_window().forward_10_seconds_button_for_testing();
  ASSERT_NE(nullptr, replay_10_seconds_button);
  ASSERT_NE(nullptr, forward_10_seconds_button);
  EXPECT_TRUE(replay_10_seconds_button->IsDrawn());
  EXPECT_TRUE(forward_10_seconds_button->IsDrawn());

  views::test::ButtonTestApi replay_button_clicker(replay_10_seconds_button);
  views::test::ButtonTestApi forward_button_clicker(forward_10_seconds_button);
  ui::MouseEvent dummy_event(ui::EventType::kMousePressed, gfx::Point(0, 0),
                             gfx::Point(0, 0), ui::EventTimeForNow(), 0, 0);

  // Clicking the replay 10 seconds button should seek backwards 10 seconds.
  PictureInPictureWindowManager::GetInstance()
      ->set_window_controller_for_testing(&pip_window_controller());
  EXPECT_CALL(pip_window_controller(), SeekTo(base::Seconds(32)));
  replay_button_clicker.NotifyClick(dummy_event);
  testing::Mock::VerifyAndClearExpectations(&pip_window_controller());

  // Clicking the forward 10 seconds button should seek forwards 10 seconds.
  EXPECT_CALL(pip_window_controller(), SeekTo(base::Seconds(52)));
  forward_button_clicker.NotifyClick(dummy_event);
  testing::Mock::VerifyAndClearExpectations(&pip_window_controller());

  // Clicking the replay 10 seconds button less than 10 seconds into the video
  // should seek to the beginning.
  media_session::MediaPosition early_media_position(
      /*playback_rate=*/0,
      /*duration=*/base::Seconds(100),
      /*position=*/base::Seconds(4),
      /*end_of_media=*/false);
  overlay_window().SetMediaPosition(early_media_position);
  EXPECT_CALL(pip_window_controller(), SeekTo(base::Seconds(0)));
  replay_button_clicker.NotifyClick(dummy_event);
  testing::Mock::VerifyAndClearExpectations(&pip_window_controller());

  // Clicking the forward 10 seconds button with less than 10 seconds left in
  // the video should seek to the end.
  media_session::MediaPosition late_media_position(
      /*playback_rate=*/0,
      /*duration=*/base::Seconds(100),
      /*position=*/base::Seconds(97),
      /*end_of_media=*/false);
  overlay_window().SetMediaPosition(late_media_position);
  EXPECT_CALL(pip_window_controller(), SeekTo(base::Seconds(100)));
  forward_button_clicker.NotifyClick(dummy_event);
  testing::Mock::VerifyAndClearExpectations(&pip_window_controller());
}

// Gesture events are not supported on Mac.
#if !BUILDFLAG(IS_MAC)
TEST_F(VideoOverlayWindowViewsTest,
       ReplayAndForward10SecondsSeekVideo_GestureTap) {
  overlay_window().ShowInactive();
  overlay_window().SetPlayPauseButtonVisibility(true);
  overlay_window().ForceControlsVisibleForTesting(true);
  media_session::MediaPosition media_position(/*playback_rate=*/0,
                                              /*duration=*/base::Seconds(100),
                                              /*position=*/base::Seconds(42),
                                              /*end_of_media=*/false);
  overlay_window().SetMediaPosition(media_position);
  WaitForLayout();

  SimpleOverlayWindowImageButton* replay_10_seconds_button =
      overlay_window().replay_10_seconds_button_for_testing();
  SimpleOverlayWindowImageButton* forward_10_seconds_button =
      overlay_window().forward_10_seconds_button_for_testing();
  ASSERT_NE(nullptr, replay_10_seconds_button);
  ASSERT_NE(nullptr, forward_10_seconds_button);
  EXPECT_TRUE(replay_10_seconds_button->IsDrawn());
  EXPECT_TRUE(forward_10_seconds_button->IsDrawn());

  // Tapping the replay 10 seconds button should seek backwards 10 seconds.
  PictureInPictureWindowManager::GetInstance()
      ->set_window_controller_for_testing(&pip_window_controller());
  EXPECT_CALL(pip_window_controller(), SeekTo(base::Seconds(32)));
  GestureTapOnView(replay_10_seconds_button);
  testing::Mock::VerifyAndClearExpectations(&pip_window_controller());

  // Tapping the forward 10 seconds button should seek forwards 10 seconds.
  EXPECT_CALL(pip_window_controller(), SeekTo(base::Seconds(52)));
  GestureTapOnView(forward_10_seconds_button);
  testing::Mock::VerifyAndClearExpectations(&pip_window_controller());

  // Tapping the replay 10 seconds button less than 10 seconds into the video
  // should seek to the beginning.
  media_session::MediaPosition early_media_position(
      /*playback_rate=*/0,
      /*duration=*/base::Seconds(100),
      /*position=*/base::Seconds(4),
      /*end_of_media=*/false);
  overlay_window().SetMediaPosition(early_media_position);
  EXPECT_CALL(pip_window_controller(), SeekTo(base::Seconds(0)));
  GestureTapOnView(replay_10_seconds_button);
  testing::Mock::VerifyAndClearExpectations(&pip_window_controller());

  // Tapping the forward 10 seconds button with less than 10 seconds left in
  // the video should seek to the end.
  media_session::MediaPosition late_media_position(
      /*playback_rate=*/0,
      /*duration=*/base::Seconds(100),
      /*position=*/base::Seconds(97),
      /*end_of_media=*/false);
  overlay_window().SetMediaPosition(late_media_position);
  EXPECT_CALL(pip_window_controller(), SeekTo(base::Seconds(100)));
  GestureTapOnView(forward_10_seconds_button);
  testing::Mock::VerifyAndClearExpectations(&pip_window_controller());
}
#endif  // BUILDFLAG(IS_MAC)

TEST_F(VideoOverlayWindowViewsTest, ReplayAndForward10SecondsOnKeyPress) {
  overlay_window().ForceControlsVisibleForTesting(true);
  media_session::MediaPosition media_position(/*playback_rate=*/0,
                                              /*duration=*/base::Seconds(100),
                                              /*position=*/base::Seconds(42),
                                              /*end_of_media=*/false);
  overlay_window().SetMediaPosition(media_position);

  ui::KeyEvent left_event(ui::EventType::kKeyPressed, ui::VKEY_LEFT,
                          ui::EF_NONE);
  ui::KeyEvent right_event(ui::EventType::kKeyPressed, ui::VKEY_RIGHT,
                           ui::EF_NONE);

  // The left arrow key should seek the video backwards 10 seconds.
  PictureInPictureWindowManager::GetInstance()
      ->set_window_controller_for_testing(&pip_window_controller());
  EXPECT_CALL(pip_window_controller(), SeekTo(base::Seconds(32)));
  overlay_window().OnKeyEvent(&left_event);
  testing::Mock::VerifyAndClearExpectations(&pip_window_controller());

  // The right arrow key should seek the video forwards 10 seconds.
  EXPECT_CALL(pip_window_controller(), SeekTo(base::Seconds(52)));
  overlay_window().OnKeyEvent(&right_event);
  testing::Mock::VerifyAndClearExpectations(&pip_window_controller());
}

TEST_F(VideoOverlayWindowViewsTest, DisplaysFavicon) {
  overlay_window().ForceControlsVisibleForTesting(true);
  views::ImageView* favicon_view = overlay_window().favicon_view_for_testing();
  ASSERT_NE(nullptr, favicon_view);
  EXPECT_TRUE(favicon_view->IsDrawn());

  // Before any favicon is set, the default favicon should be displayed.
  {
    ui::ImageModel image_model = favicon_view->GetImageModel();
    EXPECT_TRUE(image_model.IsVectorIcon());
    EXPECT_EQ(
        image_model.GetVectorIcon().vector_icon(),
        &(features::IsRoundedIconsEnabled() ? vector_icons::kGlobeIcon
                                            : vector_icons::kGlobeOldIcon));
  }

  // Setting the favicon should use that instead.
  {
    media_session::MediaImage test_favicon;
    test_favicon.src = GURL("https://google.com");
    SkBitmap bitmap;
    bitmap.allocN32Pixels(100, 100);
    bitmap.eraseColor(SK_ColorBLUE);
    PictureInPictureWindowManager::GetInstance()
        ->set_window_controller_for_testing(&pip_window_controller());
    EXPECT_CALL(pip_window_controller(),
                GetMediaImageImpl(test_favicon, 16, 16))
        .WillOnce(testing::Return(bitmap));

    overlay_window().SetFaviconImages({test_favicon});
    ui::ImageModel image_model = favicon_view->GetImageModel();
    EXPECT_FALSE(image_model.IsVectorIcon());
    EXPECT_TRUE(image_model.IsImage());
  }

  // Setting the favicon back to empty should change back to the default
  // favicon.
  {
    overlay_window().SetFaviconImages({});
    ui::ImageModel image_model = favicon_view->GetImageModel();
    EXPECT_TRUE(image_model.IsVectorIcon());
    EXPECT_EQ(
        image_model.GetVectorIcon().vector_icon(),
        &(features::IsRoundedIconsEnabled() ? vector_icons::kGlobeIcon
                                            : vector_icons::kGlobeOldIcon));
  }
}

TEST_F(VideoOverlayWindowViewsTest, DisplaysOrigin) {
  overlay_window().ForceControlsVisibleForTesting(true);
  views::Label* origin = overlay_window().origin_for_testing();
  ASSERT_NE(nullptr, origin);
  EXPECT_TRUE(origin->IsDrawn());

  overlay_window().SetSourceTitle(u"google.com");
  EXPECT_EQ(origin->GetText(), u"google.com");
}

TEST_F(VideoOverlayWindowViewsTest, OriginLabelHasCorrectDirectionality) {
  views::Label* origin = overlay_window().origin_for_testing();
  ASSERT_NE(nullptr, origin);

  // The directionality should be LTR to prevent spoofing.
  EXPECT_EQ(base::i18n::LEFT_TO_RIGHT, origin->GetTextDirectionForTesting());

  // Set the source title to a RTL string.
  const char16_t kRtl[] = u"אבג";
  overlay_window().SetSourceTitle(kRtl);
  EXPECT_EQ(kRtl, origin->GetText());

  // The directionality should still be LTR to prevent spoofing.
  EXPECT_EQ(base::i18n::LEFT_TO_RIGHT, origin->GetTextDirectionForTesting());
}

TEST_F(VideoOverlayWindowViewsTest,
       ControlsNeverHideWhileProgressBarIsDragged) {
  overlay_window().ShowInactive();
  overlay_window().SetPlayPauseButtonVisibility(true);
  overlay_window().ForceControlsVisibleForTesting(true);

  // Move time forward to ensure controls layout is completed.
  WaitForLayout();

  global_media_controls::MediaProgressView* progress_view =
      overlay_window().progress_view_for_testing();
  ASSERT_NE(nullptr, progress_view);
  EXPECT_TRUE(progress_view->IsDrawn());

  // Start dragging.
  gfx::Point point(progress_view->width() / 2, progress_view->height() / 2);
  ui::MouseEvent pressed_event(ui::EventType::kMousePressed, point, point,
                               ui::EventTimeForNow(), ui::EF_LEFT_MOUSE_BUTTON,
                               ui::EF_LEFT_MOUSE_BUTTON);
  progress_view->OnMousePressed(pressed_event);

  // Move time forward to ensure drag delay timer has fired.
  task_environment()->FastForwardBy(base::Seconds(1));

  // While dragging, the controls should remain visible. We'll need to stop
  // forcing them visible for testing and then wait for the hide timer to fire.
  overlay_window().StopForcingControlsVisibleForTesting();
  task_environment()->FastForwardBy(base::Seconds(7));
  EXPECT_TRUE(overlay_window().GetControlsContainerView()->IsDrawn());

  ui::MouseEvent released_event = ui::MouseEvent(
      ui::EventType::kMouseReleased, point, point, ui::EventTimeForNow(),
      ui::EF_LEFT_MOUSE_BUTTON, ui::EF_LEFT_MOUSE_BUTTON);
  progress_view->OnMouseReleased(released_event);

  // Once the drag ends, the controls should be able to hide.
  task_environment()->FastForwardBy(base::Seconds(7));
  EXPECT_TRUE(overlay_window().GetControlsContainerView()->IsDrawn());
}

TEST_F(VideoOverlayWindowViewsTest, LiveStatusShownForLiveVideos) {
  overlay_window().ForceControlsVisibleForTesting(true);
  views::Label* timestamp = overlay_window().timestamp_for_testing();
  views::Label* live_status = overlay_window().live_status_for_testing();
  ASSERT_NE(nullptr, timestamp);
  ASSERT_NE(nullptr, live_status);

  // The timestamp should start out visible while the live status should start
  // out hidden.
  EXPECT_FALSE(live_status->GetVisible());
  EXPECT_TRUE(timestamp->GetVisible());

  // Setting the position to live should hide the timestamp and show the live
  // status.
  media_session::MediaPosition live_media_position(
      /*playback_rate=*/0,
      /*duration=*/base::TimeDelta::Max(),
      /*position=*/base::Seconds(42),
      /*end_of_media=*/false);
  overlay_window().SetMediaPosition(live_media_position);
  EXPECT_TRUE(live_status->GetVisible());
  EXPECT_FALSE(timestamp->GetVisible());

  // Setting the position to a non-live video should hide the live status and
  // show the timestamp.
  media_session::MediaPosition media_position(/*playback_rate=*/0,
                                              /*duration=*/base::Seconds(100),
                                              /*position=*/base::Seconds(42),
                                              /*end_of_media=*/false);
  overlay_window().SetMediaPosition(media_position);
  EXPECT_FALSE(live_status->GetVisible());
  EXPECT_TRUE(timestamp->GetVisible());
}

TEST_F(VideoOverlayWindowViewsTest, NextAndPreviousShareVisibility) {
  overlay_window().ForceControlsVisibleForTesting(true);
  SimpleOverlayWindowImageButton* next_button =
      overlay_window().next_track_controls_view_for_testing();
  SimpleOverlayWindowImageButton* prev_button =
      overlay_window().previous_track_controls_view_for_testing();

  ASSERT_NE(nullptr, next_button);
  ASSERT_NE(nullptr, prev_button);

  // If only "nexttrack" is enabled, both buttons are shown but only the next
  // button is enabled.
  overlay_window().SetNextTrackButtonVisibility(true);
  WaitForLayout();
  EXPECT_TRUE(next_button->GetVisible());
  EXPECT_TRUE(prev_button->GetVisible());
  EXPECT_TRUE(next_button->GetEnabled());
  EXPECT_FALSE(prev_button->GetEnabled());

  // If both previous and next track are enabled, then both buttons are shown
  // and enabled.
  overlay_window().SetPreviousTrackButtonVisibility(true);
  WaitForLayout();
  EXPECT_TRUE(next_button->GetVisible());
  EXPECT_TRUE(prev_button->GetVisible());
  EXPECT_TRUE(next_button->GetEnabled());
  EXPECT_TRUE(prev_button->GetEnabled());

  // When disabled again, the buttons are hidden.
  overlay_window().SetNextTrackButtonVisibility(false);
  overlay_window().SetPreviousTrackButtonVisibility(false);
  WaitForLayout();
  EXPECT_FALSE(next_button->GetVisible());
  EXPECT_FALSE(prev_button->GetVisible());

  // If only "previousslide" is enabled, both buttons are shown but only the
  // previous button is enabled.
  overlay_window().SetPreviousSlideButtonVisibility(true);
  WaitForLayout();
  EXPECT_TRUE(next_button->GetVisible());
  EXPECT_TRUE(prev_button->GetVisible());
  EXPECT_FALSE(next_button->GetEnabled());
  EXPECT_TRUE(prev_button->GetEnabled());

  // If both previous and next slide are enabled, then both buttons are shown
  // and enabled.
  overlay_window().SetNextSlideButtonVisibility(true);
  WaitForLayout();
  EXPECT_TRUE(next_button->GetVisible());
  EXPECT_TRUE(prev_button->GetVisible());
  EXPECT_TRUE(next_button->GetEnabled());
  EXPECT_TRUE(prev_button->GetEnabled());

  // When disabled again, the buttons are hidden.
  overlay_window().SetNextSlideButtonVisibility(false);
  overlay_window().SetPreviousSlideButtonVisibility(false);
  WaitForLayout();
  EXPECT_FALSE(next_button->GetVisible());
  EXPECT_FALSE(prev_button->GetVisible());
}

TEST_F(VideoOverlayWindowViewsTest,
       FastForwardAndRewindAreHiddenForLiveVideos) {
  overlay_window().ForceControlsVisibleForTesting(true);
  SimpleOverlayWindowImageButton* replay_10_seconds_button =
      overlay_window().replay_10_seconds_button_for_testing();
  SimpleOverlayWindowImageButton* forward_10_seconds_button =
      overlay_window().forward_10_seconds_button_for_testing();
  ASSERT_NE(nullptr, replay_10_seconds_button);
  ASSERT_NE(nullptr, forward_10_seconds_button);

  // For live media, the fast-forward and rewind buttons should be hidden.
  media_session::MediaPosition live_media_position(
      /*playback_rate=*/0,
      /*duration=*/base::TimeDelta::Max(),
      /*position=*/base::Seconds(42),
      /*end_of_media=*/false);
  overlay_window().SetMediaPosition(live_media_position);
  EXPECT_FALSE(replay_10_seconds_button->GetVisible());
  EXPECT_FALSE(forward_10_seconds_button->GetVisible());
}

TEST_F(VideoOverlayWindowViewsTest, VideoConferencingUI) {
  overlay_window().ForceControlsVisibleForTesting(true);
  ToggleCameraButton* toggle_camera_button =
      overlay_window().toggle_camera_button_for_testing();
  ToggleMicrophoneButton* toggle_microphone_button =
      overlay_window().toggle_microphone_button_for_testing();
  HangUpButton* hang_up_button = overlay_window().hang_up_button_for_testing();
  global_media_controls::MediaProgressView* progress_view =
      overlay_window().progress_view_for_testing();

  ASSERT_NE(nullptr, toggle_camera_button);
  ASSERT_NE(nullptr, toggle_microphone_button);
  ASSERT_NE(nullptr, hang_up_button);
  ASSERT_NE(nullptr, progress_view);

  // The VC controls should start hidden, and other controls should be shown.
  WaitForLayout();
  EXPECT_FALSE(toggle_camera_button->IsDrawn());
  EXPECT_FALSE(toggle_microphone_button->IsDrawn());
  EXPECT_FALSE(hang_up_button->IsDrawn());
  EXPECT_TRUE(progress_view->IsDrawn());

  // If at least one VC control is available, it should be shown and the non-VC
  // controls should be hidden.
  overlay_window().SetHangUpButtonVisibility(true);
  WaitForLayout();
  EXPECT_FALSE(toggle_camera_button->IsDrawn());
  EXPECT_FALSE(toggle_microphone_button->IsDrawn());
  EXPECT_TRUE(hang_up_button->IsDrawn());
  EXPECT_FALSE(progress_view->IsDrawn());

  // Enabling other VC controls should show those.
  overlay_window().SetToggleCameraButtonVisibility(true);
  overlay_window().SetToggleMicrophoneButtonVisibility(true);
  WaitForLayout();
  EXPECT_TRUE(toggle_camera_button->IsDrawn());
  EXPECT_TRUE(toggle_microphone_button->IsDrawn());
  EXPECT_TRUE(hang_up_button->IsDrawn());
  EXPECT_FALSE(progress_view->IsDrawn());

  // Disabling all VC controls should show the non-VC controls again.
  overlay_window().SetHangUpButtonVisibility(false);
  overlay_window().SetToggleCameraButtonVisibility(false);
  overlay_window().SetToggleMicrophoneButtonVisibility(false);
  WaitForLayout();
  EXPECT_FALSE(toggle_camera_button->IsDrawn());
  EXPECT_FALSE(toggle_microphone_button->IsDrawn());
  EXPECT_FALSE(hang_up_button->IsDrawn());
  EXPECT_TRUE(progress_view->IsDrawn());
}

TEST_F(VideoOverlayWindowViewsTest, LiveCaption) {
  overlay_window().ForceControlsVisibleForTesting(true);
  profile().GetPrefs()->SetBoolean(prefs::kLiveCaptionEnabled, false);
  OverlayWindowLiveCaptionButton* live_caption_button =
      overlay_window().live_caption_button_for_testing();
  OverlayWindowLiveCaptionDialog* live_caption_dialog =
      overlay_window().live_caption_dialog_for_testing();

  ASSERT_NE(nullptr, live_caption_button);
  ASSERT_NE(nullptr, live_caption_dialog);

  {
    // The accessible data of the toggle button should show the state as
    // collapsed.
    ui::AXNodeData node_data;
    live_caption_button->GetViewAccessibility().GetAccessibleNodeData(
        &node_data);
    EXPECT_FALSE(node_data.HasState(ax::mojom::State::kExpanded));
    EXPECT_TRUE(node_data.HasState(ax::mojom::State::kCollapsed));
  }

  // The live caption button should start visible and the live caption dialog
  // should start invisible.
  WaitForLayout();
  EXPECT_TRUE(live_caption_button->IsDrawn());
  EXPECT_FALSE(live_caption_dialog->IsDrawn());

  // Pressing the live caption button should display the live caption dialog.
  views::test::ButtonTestApi live_caption_button_clicker(live_caption_button);
  ui::MouseEvent dummy_event(ui::EventType::kMousePressed, gfx::Point(0, 0),
                             gfx::Point(0, 0), ui::EventTimeForNow(), 0, 0);
  live_caption_button_clicker.NotifyClick(dummy_event);
  WaitForLayout();
  EXPECT_TRUE(live_caption_dialog->IsDrawn());

  {
    // The accessible data of the toggle button should show the state as
    // expanded once the dialog is there.
    ui::AXNodeData node_data;
    live_caption_button->GetViewAccessibility().GetAccessibleNodeData(
        &node_data);
    EXPECT_TRUE(node_data.HasState(ax::mojom::State::kExpanded));
    EXPECT_FALSE(node_data.HasState(ax::mojom::State::kCollapsed));
  }

  // The live caption button should be enabled and toggled off, while the live
  // translate button should be disabled and toggled off.
  EXPECT_FALSE(profile().GetPrefs()->GetBoolean(prefs::kLiveCaptionEnabled));
  views::ToggleButton* live_caption_toggle_button =
      live_caption_dialog->live_caption_button_for_testing();
  views::ToggleButton* live_translate_toggle_button =
      live_caption_dialog->live_translate_button_for_testing();
  EXPECT_TRUE(live_caption_toggle_button->GetEnabled());
  EXPECT_FALSE(live_caption_toggle_button->GetIsOn());
  EXPECT_FALSE(live_translate_toggle_button->GetEnabled());
  EXPECT_FALSE(live_translate_toggle_button->GetIsOn());

  // Toggling the live caption button should enable the live translate button
  // and also enable the live caption pref.
  views::test::ButtonTestApi live_caption_toggle_button_clicker(
      live_caption_toggle_button);
  live_caption_toggle_button_clicker.NotifyClick(dummy_event);
  WaitForLayout();
  EXPECT_TRUE(profile().GetPrefs()->GetBoolean(prefs::kLiveCaptionEnabled));
  EXPECT_TRUE(live_caption_toggle_button->GetIsOn());
  EXPECT_TRUE(live_translate_toggle_button->GetEnabled());
  EXPECT_FALSE(live_translate_toggle_button->GetIsOn());

  // Turn off LiveCaption, because leaving it on breaks the test harness when it
  // tries to destroy the UI.
  profile().GetPrefs()->SetBoolean(prefs::kLiveCaptionEnabled, false);
}

TEST_F(VideoOverlayWindowViewsTest, LiveCaption_MouseClickOutside) {
  overlay_window().ForceControlsVisibleForTesting(true);
  OverlayWindowLiveCaptionButton* live_caption_button =
      overlay_window().live_caption_button_for_testing();
  OverlayWindowLiveCaptionDialog* live_caption_dialog =
      overlay_window().live_caption_dialog_for_testing();

  ASSERT_NE(nullptr, live_caption_button);
  ASSERT_NE(nullptr, live_caption_dialog);

  // The live caption button should start visible and the live caption dialog
  // should start invisible.
  WaitForLayout();
  EXPECT_TRUE(live_caption_button->IsDrawn());
  EXPECT_FALSE(live_caption_dialog->IsDrawn());

  // Pressing the live caption button should display the live caption dialog.
  views::test::ButtonTestApi live_caption_button_clicker(live_caption_button);
  ui::MouseEvent dummy_event(ui::EventType::kMousePressed, gfx::Point(0, 0),
                             gfx::Point(0, 0), ui::EventTimeForNow(), 0, 0);
  live_caption_button_clicker.NotifyClick(dummy_event);
  WaitForLayout();
  EXPECT_TRUE(live_caption_dialog->IsDrawn());

  // Clicking outside of the live caption dialog should close the live caption
  // dialog.
  gfx::Point outside_point = live_caption_dialog->bounds().origin();
  outside_point.Offset(-20, -20);
  ui::MouseEvent click_outside_event(ui::EventType::kMousePressed,
                                     outside_point, outside_point,
                                     ui::EventTimeForNow(), 0, 0);
  overlay_window().OnMouseEvent(&click_outside_event);
  EXPECT_FALSE(live_caption_dialog->IsDrawn());
}

// Gesture events are not supported on Mac.
#if !BUILDFLAG(IS_MAC)

TEST_F(VideoOverlayWindowViewsTest, LiveCaption_GestureTapOutside) {
  overlay_window().ForceControlsVisibleForTesting(true);
  OverlayWindowLiveCaptionButton* live_caption_button =
      overlay_window().live_caption_button_for_testing();
  OverlayWindowLiveCaptionDialog* live_caption_dialog =
      overlay_window().live_caption_dialog_for_testing();

  ASSERT_NE(nullptr, live_caption_button);
  ASSERT_NE(nullptr, live_caption_dialog);

  // The live caption button should start visible and the live caption dialog
  // should start invisible.
  WaitForLayout();
  EXPECT_TRUE(live_caption_button->IsDrawn());
  EXPECT_FALSE(live_caption_dialog->IsDrawn());

  // Pressing the live caption button should display the live caption dialog.
  views::test::ButtonTestApi live_caption_button_clicker(live_caption_button);
  ui::MouseEvent dummy_event(ui::EventType::kMousePressed, gfx::Point(0, 0),
                             gfx::Point(0, 0), ui::EventTimeForNow(), 0, 0);
  live_caption_button_clicker.NotifyClick(dummy_event);
  WaitForLayout();
  EXPECT_TRUE(live_caption_dialog->IsDrawn());

  // Tapping outside of the live caption dialog should close the live caption
  // dialog.
  gfx::Point outside_point = live_caption_dialog->bounds().origin();
  outside_point.Offset(-20, -20);
  ui::GestureEvent tap_outside_event(
      outside_point.x(), outside_point.y(), 0, base::TimeTicks::Now(),
      ui::GestureEventDetails(ui::EventType::kGestureTap));
  overlay_window().OnGestureEvent(&tap_outside_event);
  EXPECT_FALSE(live_caption_dialog->IsDrawn());
}

TEST_F(VideoOverlayWindowViewsTest, LiveCaption_GestureTap) {
  overlay_window().ShowInactive();
  overlay_window().SetPlayPauseButtonVisibility(true);
  overlay_window().ForceControlsVisibleForTesting(true);

  OverlayWindowLiveCaptionButton* live_caption_button =
      overlay_window().live_caption_button_for_testing();
  OverlayWindowLiveCaptionDialog* live_caption_dialog =
      overlay_window().live_caption_dialog_for_testing();
  views::ToggleButton* live_caption_toggle_button =
      live_caption_dialog->live_caption_button_for_testing();
  views::ToggleButton* live_translate_toggle_button =
      live_caption_dialog->live_translate_button_for_testing();
  views::Combobox* target_language_combobox =
      live_caption_dialog->target_language_combobox_for_testing();

  profile().GetPrefs()->SetBoolean(prefs::kLiveCaptionEnabled, false);
  profile().GetPrefs()->SetBoolean(prefs::kLiveTranslateEnabled, false);

  ASSERT_NE(nullptr, live_caption_button);
  ASSERT_NE(nullptr, live_caption_dialog);
  ASSERT_NE(nullptr, live_caption_toggle_button);
  ASSERT_NE(nullptr, live_translate_toggle_button);
  ASSERT_NE(nullptr, target_language_combobox);

  // Click the live caption button to display the live caption dialog.
  WaitForLayout();
  views::test::ButtonTestApi live_caption_button_clicker(live_caption_button);
  ui::MouseEvent dummy_event(ui::EventType::kMousePressed, gfx::Point(0, 0),
                             gfx::Point(0, 0), ui::EventTimeForNow(), 0, 0);
  live_caption_button_clicker.NotifyClick(dummy_event);
  WaitForLayout();
  EXPECT_TRUE(live_caption_dialog->IsDrawn());

  // Tap the live caption toggle button to enable live caption.
  EXPECT_FALSE(profile().GetPrefs()->GetBoolean(prefs::kLiveCaptionEnabled));
  GestureTapOnView(live_caption_toggle_button);
  WaitForLayout();
  EXPECT_TRUE(profile().GetPrefs()->GetBoolean(prefs::kLiveCaptionEnabled));

  // Tap the live translate button to enable live translate.
  EXPECT_FALSE(profile().GetPrefs()->GetBoolean(prefs::kLiveTranslateEnabled));
  GestureTapOnView(live_translate_toggle_button);
  WaitForLayout();
  EXPECT_TRUE(profile().GetPrefs()->GetBoolean(prefs::kLiveTranslateEnabled));

  // Tap the target language combobox to open the target language selection.
  EXPECT_FALSE(target_language_combobox->IsMenuRunning());
  GestureTapOnView(target_language_combobox);
  WaitForLayout();
  EXPECT_TRUE(target_language_combobox->IsMenuRunning());

  profile().GetPrefs()->SetBoolean(prefs::kLiveCaptionEnabled, false);
  profile().GetPrefs()->SetBoolean(prefs::kLiveTranslateEnabled, false);
}

#endif  // BUILDFLAG(IS_MAC)

TEST_F(VideoOverlayWindowViewsTest, InitialTitleAndScrimVisibility) {
  overlay_window().ForceControlsVisibleForTesting(false);
  overlay_window().ShowInactive();
  WaitForLayout();

  // The initial title hide timer should be running.
  EXPECT_TRUE(
      overlay_window().initial_title_hide_timer_for_testing().IsRunning());

  // Title and scrim should be visible.
  EXPECT_TRUE(overlay_window().AreTitleAndScrimVisibleForTesting());
}

TEST_F(VideoOverlayWindowViewsTest, TitleAndScrimHideAfterTimer) {
  overlay_window().ShowInactive();
  WaitForLayout();

  // Fast forward time to fire the timer.
  task_environment()->FastForwardBy(
      VideoOverlayWindowViews::kTitleShowDuration);

  // Timer should not be running anymore.
  EXPECT_FALSE(
      overlay_window().initial_title_hide_timer_for_testing().IsRunning());

  // Title and scrim should now be animating to hidden.
  EXPECT_FALSE(overlay_window().AreTitleAndScrimVisibleForTesting());
  EXPECT_FALSE(overlay_window().AreControlsVisible());
}

TEST_F(VideoOverlayWindowViewsTest, MouseHoverShowsAllControls) {
  overlay_window().ShowInactive();
  WaitForLayout();

  // Move mouse over the window.
  const auto controls_top_scrim_bounds =
      overlay_window().controls_top_scrim_view_for_testing()->bounds();
  const gfx::Point moved_location(controls_top_scrim_bounds.CenterPoint());
  ui::MouseEvent moved_event(ui::EventType::kMouseMoved, moved_location,
                             moved_location, ui::EventTimeForNow(), ui::EF_NONE,
                             ui::EF_NONE);
  overlay_window().OnMouseEvent(&moved_event);

  // Timer should not be running anymore.
  EXPECT_FALSE(
      overlay_window().initial_title_hide_timer_for_testing().IsRunning());

  // All controls should now be visible or animating to visible.
  EXPECT_TRUE(overlay_window().AreTitleAndScrimVisibleForTesting());
  EXPECT_TRUE(overlay_window().AreControlsVisible());
}

TEST_F(VideoOverlayWindowViewsTest, PlaybackControlsContainerVisibility) {
  overlay_window().ShowInactive();
  overlay_window().ForceControlsVisibleForTesting(true);
  overlay_window().SetPlayPauseButtonVisibility(true);
  WaitForLayout();

  views::View* playback_controls_container =
      overlay_window().playback_controls_container_for_testing();
  ASSERT_NE(nullptr, playback_controls_container);

  // Initially, the playback_controls_container should be drawn.
  EXPECT_TRUE(playback_controls_container->IsDrawn());

  // Verify that the playback_controls_container is hidden.
  overlay_window().SetPlaybackControlsVisibility(false);
  WaitForLayout();
  EXPECT_FALSE(playback_controls_container->IsDrawn());

  // Providing a new surface should restore the playback_controls_container.
  const viz::SurfaceId surface_id(viz::FrameSinkId(1, 1),
                                  viz::LocalSurfaceId());
  overlay_window().SetSurfaceId(surface_id);
  WaitForLayout();
  EXPECT_TRUE(playback_controls_container->IsDrawn());
}

TEST_F(VideoOverlayWindowViewsTest, TopControlsAreAlwaysOnTheRight) {
  const gfx::Rect work_area(0, 0, 4000, 4000);
  SetDisplayWorkArea(work_area);
  overlay_window().ShowInactive();
  overlay_window().ForceControlsVisibleForTesting(true, true);

  constexpr int kMargin = 100;
  const gfx::Size window_size(500, 500);

  const gfx::Rect top_left_quadrant_bounds(gfx::Point(kMargin, kMargin),
                                           window_size);
  const gfx::Rect top_right_quadrant_bounds(
      gfx::Point(work_area.width() - window_size.width() - kMargin, kMargin),
      window_size);
  const gfx::Rect bottom_left_quadrant_bounds(
      gfx::Point(kMargin, work_area.height() - window_size.height() - kMargin),
      window_size);
  const gfx::Rect bottom_right_quadrant_bounds(
      gfx::Point(work_area.width() - window_size.width() - kMargin,
                 work_area.height() - window_size.height() - kMargin),
      window_size);

  struct {
    std::string name;
    gfx::Rect bounds;
  } quadrant_test_cases[] = {
      {"Top-left", top_left_quadrant_bounds},
      {"Top-right", top_right_quadrant_bounds},
      {"Bottom-left", bottom_left_quadrant_bounds},
      {"Bottom-right", bottom_right_quadrant_bounds},
  };

  for (const auto& test_case : quadrant_test_cases) {
    SCOPED_TRACE(testing::Message() << "Quadrant: " << test_case.name);
    overlay_window().SetBounds(test_case.bounds);
    overlay_window().OnUpdateControlsBounds();

    // The top controls (minimize, back-to-tab, close) should always be on the
    // top-right of the picture-in-picture window, regardless of which quadrant
    // the window is in.
    const auto pip_window_bounds = overlay_window().GetWindowBoundsInScreen();
    const auto check_control_bounds = [&](const views::View* button_view) {
      EXPECT_GT(button_view->GetMirroredBounds().x(),
                pip_window_bounds.width() / 2);
      EXPECT_LT(button_view->GetMirroredBounds().y(),
                pip_window_bounds.height() / 2);
    };

    check_control_bounds(overlay_window().close_button_for_testing());
    check_control_bounds(overlay_window().minimize_button_for_testing());
    check_control_bounds(overlay_window().back_to_tab_button_for_testing());
  }
}

class VideoOverlayWindowWithShowAnimationTest
    : public VideoOverlayWindowViewsTest {
 public:
  void SetUp() override {
    VideoOverlayWindowViewsTest::SetUp();
#if BUILDFLAG(IS_WIN)
    GTEST_SKIP() << "Fade in animation is disabled on Windows.";
#endif
  }
};

TEST_F(VideoOverlayWindowWithShowAnimationTest,
       FadeInAnimationIsUsedOnWindowShow) {
  // ShowInactive should trigger the fade-in animation.
  overlay_window().ShowInactive();

  // Get the PiP fade animator.
  PictureInPictureWidgetFadeAnimator* pip_fade_animator =
      overlay_window().get_fade_animator_for_testing();
  EXPECT_NE(nullptr, pip_fade_animator);

  // Get the widget fade animator and verify that it is fading in.
  views::WidgetFadeAnimator* widget_fade_animator =
      pip_fade_animator->GetWidgetFadeAnimatorForTesting();
  EXPECT_NE(nullptr, widget_fade_animator);
  EXPECT_TRUE(widget_fade_animator->IsFadingIn());
}

TEST_F(VideoOverlayWindowWithShowAnimationTest,
       FadeInAnimationIsCancelledOnHide) {
  overlay_window().ShowInactive();

  // Get the PiP fade animator.
  PictureInPictureWidgetFadeAnimator* pip_fade_animator =
      overlay_window().get_fade_animator_for_testing();
  EXPECT_NE(nullptr, pip_fade_animator);

  // Get the widget fade animator and verify that it is fading in.
  views::WidgetFadeAnimator* widget_fade_animator =
      pip_fade_animator->GetWidgetFadeAnimatorForTesting();
  EXPECT_NE(nullptr, widget_fade_animator);
  EXPECT_TRUE(widget_fade_animator->IsFadingIn());

  // Hiding the window should cancel the animation.
  overlay_window().Hide();
  EXPECT_EQ(nullptr, overlay_window()
                         .get_fade_animator_for_testing()
                         ->GetWidgetFadeAnimatorForTesting());
}

TEST_F(VideoOverlayWindowWithShowAnimationTest,
       FadeInAnimationIsCancelledOnClose) {
  overlay_window().ShowInactive();

  // Get the PiP fade animator.
  PictureInPictureWidgetFadeAnimator* pip_fade_animator =
      overlay_window().get_fade_animator_for_testing();
  EXPECT_NE(nullptr, pip_fade_animator);

  // Get the widget fade animator and verify that it is fading in.
  views::WidgetFadeAnimator* widget_fade_animator =
      pip_fade_animator->GetWidgetFadeAnimatorForTesting();
  EXPECT_NE(nullptr, widget_fade_animator);
  EXPECT_TRUE(widget_fade_animator->IsFadingIn());

  // Closing the window should cancel the animation.
  overlay_window().Close();
  EXPECT_EQ(nullptr, overlay_window()
                         .get_fade_animator_for_testing()
                         ->GetWidgetFadeAnimatorForTesting());
}

TEST_F(VideoOverlayWindowWithShowAnimationTest,
       AnimatorIsResetWhenWidgetIsDestroyedDuringAnimation) {
  overlay_window().ShowInactive();

  // Get the PiP fade animator.
  PictureInPictureWidgetFadeAnimator* pip_fade_animator =
      overlay_window().get_fade_animator_for_testing();
  EXPECT_NE(nullptr, pip_fade_animator);

  // Get the widget fade animator and verify that it is fading in.
  views::WidgetFadeAnimator* widget_fade_animator =
      pip_fade_animator->GetWidgetFadeAnimatorForTesting();
  EXPECT_NE(nullptr, widget_fade_animator);
  EXPECT_TRUE(widget_fade_animator->IsFadingIn());

  // Destroying the widget during the animation should not crash.
  DestroyOverlayWindow();
}

// Test fixture with kPictureInPictureMuteControl enabled.
class VideoOverlayWindowWithMuteControlTest
    : public VideoOverlayWindowViewsTest {
 public:
  void SetUp() override {
    AddEnabledFeature(media::kPictureInPictureMuteControl);
    VideoOverlayWindowViewsTest::SetUp();
  }
};

// Test fixture with kPictureInPictureMuteControl disabled.
class VideoOverlayWindowWithMuteControlDisabledTest
    : public VideoOverlayWindowViewsTest {
 public:
  void SetUp() override {
    AddDisabledFeature(media::kPictureInPictureMuteControl);
    VideoOverlayWindowViewsTest::SetUp();
  }
};

// When the feature is disabled, the mute button should not be
// created and SetMediaMuted should be a no-op.
TEST_F(VideoOverlayWindowWithMuteControlDisabledTest,
       ToggleMuteButton_FeatureFlagDisabled) {
  EXPECT_EQ(nullptr, overlay_window().toggle_mute_button_for_testing());

  // Calling SetMediaMuted with no button should not crash.
  overlay_window().SetMediaMuted(true);
  overlay_window().SetMediaMuted(false);
}

// When the feature is enabled, the mute button should be created.
TEST_F(VideoOverlayWindowWithMuteControlTest,
       ToggleMuteButton_FeatureFlagEnabled) {
  ToggleMuteButton* toggle_mute_button =
      overlay_window().toggle_mute_button_for_testing();
  ASSERT_NE(nullptr, toggle_mute_button);
}

// The mute button should be visible in playback mode but hidden when video
// conferencing controls are shown.
TEST_F(VideoOverlayWindowWithMuteControlTest, ToggleMuteButton_Visibility) {
  ToggleMuteButton* toggle_mute_button =
      overlay_window().toggle_mute_button_for_testing();
  ASSERT_NE(nullptr, toggle_mute_button);

  // Trigger a controls layout update so the mute button gets positioned and
  // made visible for playback mode.
  overlay_window().SetPlayPauseButtonVisibility(true);
  overlay_window().ForceControlsVisibleForTesting(true);
  WaitForLayout();

  // In playback mode, the mute button should be drawn.
  EXPECT_TRUE(toggle_mute_button->IsDrawn());

  // When VC controls are shown, the mute button should be hidden.
  overlay_window().SetHangUpButtonVisibility(true);
  WaitForLayout();
  EXPECT_FALSE(toggle_mute_button->IsDrawn());

  // When VC controls are hidden again, the mute button should reappear.
  overlay_window().SetHangUpButtonVisibility(false);
  WaitForLayout();
  EXPECT_TRUE(toggle_mute_button->IsDrawn());
}

// SetMediaMuted should update the button's muted state.
TEST_F(VideoOverlayWindowWithMuteControlTest, ToggleMuteButton_StateToggle) {
  ToggleMuteButton* toggle_mute_button =
      overlay_window().toggle_mute_button_for_testing();
  ASSERT_NE(nullptr, toggle_mute_button);

  // Initially unmuted.
  EXPECT_FALSE(toggle_mute_button->is_muted_for_testing());

  overlay_window().SetMediaMuted(true);
  EXPECT_TRUE(toggle_mute_button->is_muted_for_testing());

  overlay_window().SetMediaMuted(false);
  EXPECT_FALSE(toggle_mute_button->is_muted_for_testing());
}

// The mute button should participate in hit testing.
TEST_F(VideoOverlayWindowWithMuteControlTest, ToggleMuteButton_HitTest) {
  ToggleMuteButton* toggle_mute_button =
      overlay_window().toggle_mute_button_for_testing();
  ASSERT_NE(nullptr, toggle_mute_button);

  // Trigger a controls layout so the mute button is positioned and visible.
  overlay_window().SetPlayPauseButtonVisibility(true);
  overlay_window().ForceControlsVisibleForTesting(true);
  WaitForLayout();
  ASSERT_TRUE(toggle_mute_button->IsDrawn());

  const gfx::Rect mute_button_bounds =
      overlay_window().GetToggleMuteButtonBounds();
  EXPECT_FALSE(mute_button_bounds.IsEmpty());
  EXPECT_TRUE(overlay_window().ControlsHitTestContainsPoint(
      mute_button_bounds.CenterPoint()));
}
