// Copyright 2013 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/frame/browser_view.h"

#include <memory>
#include <optional>

#include "base/byte_size.h"
#include "base/callback_list.h"
#include "base/feature_list.h"
#include "base/functional/bind.h"
#include "base/memory/raw_ptr.h"
#include "base/memory/scoped_refptr.h"
#include "base/numerics/safe_conversions.h"
#include "base/strings/strcat.h"
#include "base/strings/utf_string_conversions.h"
#include "base/test/metrics/histogram_tester.h"
#include "base/test/run_until.h"
#include "base/test/values_test_util.h"
#include "base/time/time.h"
#include "build/build_config.h"
#include "chrome/app/chrome_command_ids.h"
#include "chrome/app/vector_icons/vector_icons.h"
#include "chrome/browser/devtools/devtools_ui_controller.h"
#include "chrome/browser/devtools/devtools_window_testing.h"
#include "chrome/browser/policy/dm_token_utils.h"
#include "chrome/browser/preloading/scoped_prewarm_feature_list.h"
#include "chrome/browser/profiles/profile.h"
#include "chrome/browser/safe_browsing/chrome_enterprise_url_lookup_service_factory.h"
#include "chrome/browser/ui/actions/chrome_action_id.h"
#include "chrome/browser/ui/browser_actions.h"
#include "chrome/browser/ui/browser_command_controller.h"
#include "chrome/browser/ui/browser_commands.h"
#include "chrome/browser/ui/browser_commands_mac.h"
#include "chrome/browser/ui/browser_tabstrip.h"
#include "chrome/browser/ui/browser_window.h"
#include "chrome/browser/ui/browser_window/public/browser_window_features.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/create_browser_window.h"
#include "chrome/browser/ui/browser_window/public/global_browser_collection.h"
#include "chrome/browser/ui/exclusive_access/exclusive_access_context.h"
#include "chrome/browser/ui/exclusive_access/exclusive_access_manager.h"
#include "chrome/browser/ui/exclusive_access/fullscreen_controller.h"
#include "chrome/browser/ui/find_bar/find_bar.h"
#include "chrome/browser/ui/find_bar/find_bar_controller.h"
#include "chrome/browser/ui/navigator/browser_navigator.h"
#include "chrome/browser/ui/performance_controls/tab_resource_usage_tab_helper.h"
#include "chrome/browser/ui/recently_audible_helper.h"
#include "chrome/browser/ui/tab_modal_confirm_dialog.h"
#include "chrome/browser/ui/tab_ui_helper.h"
#include "chrome/browser/ui/tabs/public/tab_features.h"
#include "chrome/browser/ui/tabs/split_tab_metrics.h"
#include "chrome/browser/ui/tabs/tab_data.h"
#include "chrome/browser/ui/tabs/tab_strip_model.h"
#include "chrome/browser/ui/tabs/tab_strip_user_gesture_details.h"
#include "chrome/browser/ui/test/test_browser_ui.h"
#include "chrome/browser/ui/ui_features.h"
#include "chrome/browser/ui/views/bookmarks/bookmark_bar_view.h"
#include "chrome/browser/ui/views/bookmarks/bookmark_bar_view_observer.h"
#include "chrome/browser/ui/views/frame/browser_frame_view.h"
#include "chrome/browser/ui/views/frame/contents_container_view.h"
#include "chrome/browser/ui/views/frame/immersive_mode_tester.h"
#include "chrome/browser/ui/views/frame/layout/browser_view_layout.h"
#include "chrome/browser/ui/views/frame/multi_contents_view.h"
#include "chrome/browser/ui/views/frame/multi_contents_view_drop_target_controller.h"
#include "chrome/browser/ui/views/frame/scrim_view.h"
#include "chrome/browser/ui/views/frame/tab_strip_region_view.h"
#include "chrome/browser/ui/views/frame/toolbar_button_provider.h"
#include "chrome/browser/ui/views/frame/top_container_view.h"
#include "chrome/browser/ui/views/infobars/infobar_container_view.h"
#include "chrome/browser/ui/views/location_bar/location_bar_view.h"
#include "chrome/browser/ui/views/side_panel/side_panel.h"
#include "chrome/browser/ui/views/side_panel/side_panel_coordinator.h"
#include "chrome/browser/ui/views/tabs/tab/tab_accessibility.h"
#include "chrome/browser/ui/views/tabs/tab_strip.h"
#include "chrome/browser/ui/views/toolbar/toolbar_view.h"
#include "chrome/browser/ui/window_metadata/window_metadata_controller.h"
#include "chrome/common/chrome_features.h"
#include "chrome/common/pref_names.h"
#include "chrome/common/url_constants.h"
#include "chrome/grit/branded_strings.h"
#include "chrome/grit/generated_resources.h"
#include "chrome/test/base/chrome_test_path_utils.h"
#include "chrome/test/base/chrome_test_utils.h"
#include "chrome/test/base/in_process_browser_test.h"
#include "chrome/test/base/test_browser_window.h"
#include "chrome/test/base/ui_test_utils.h"
#include "components/bookmarks/common/bookmark_bar_visibility_state.h"
#include "components/bookmarks/common/bookmark_pref_names.h"
#include "components/enterprise/connectors/core/common.h"
#include "components/enterprise/connectors/core/connectors_prefs.h"
#include "components/enterprise/data_controls/core/browser/test_utils.h"
#include "components/keyed_service/content/browser_context_dependency_manager.h"
#include "components/policy/core/common/policy_pref_names.h"
#include "components/policy/core/common/policy_types.h"
#include "components/prefs/pref_service.h"
#include "components/safe_browsing/core/browser/realtime/fake_url_lookup_service.h"
#include "components/search/ntp_features.h"
#include "components/split_tabs/split_tab_visual_data.h"
#include "components/tabs/public/split_tab_collection.h"
#include "components/vector_icons/vector_icons.h"
#include "content/public/browser/desktop_capture_pip_utils.h"
#include "content/public/browser/invalidate_type.h"
#include "content/public/browser/web_contents.h"
#include "content/public/browser/web_contents_observer.h"
#include "content/public/common/drop_data.h"
#include "content/public/test/browser_test.h"
#include "content/public/test/browser_test_utils.h"
#include "content/public/test/navigation_simulator.h"
#include "content/public/test/scoped_accessibility_mode_override.h"
#include "content/public/test/scoped_pip_exclusion_override.h"
#include "content/public/test/test_navigation_observer.h"
#include "media/base/media_switches.h"
#include "media/capture/capture_switches.h"
#include "net/traffic_annotation/network_traffic_annotation_test_helper.h"
#include "testing/gmock/include/gmock/gmock.h"
#include "third_party/blink/public/mojom/frame/fullscreen.mojom.h"
#include "ui/accessibility/platform/ax_platform_node.h"
#include "ui/accessibility/platform/ax_platform_node_test_helper.h"
#include "ui/actions/action_id.h"
#include "ui/actions/actions.h"
#include "ui/base/accelerators/accelerator.h"
#include "ui/base/buildflags.h"
#include "ui/base/interaction/element_identifier.h"
#include "ui/base/interaction/element_tracker.h"
#include "ui/base/l10n/l10n_util.h"
#include "ui/base/metadata/metadata_header_macros.h"
#include "ui/base/metadata/metadata_impl_macros.h"
#include "ui/base/models/dialog_model.h"
#include "ui/base/ozone_buildflags.h"
#include "ui/base/ui_base_features.h"
#include "ui/events/base_event_utils.h"
#include "ui/events/event.h"
#include "ui/gfx/geometry/point.h"
#include "ui/views/accessibility/view_accessibility.h"
#include "ui/views/bubble/bubble_dialog_delegate_view.h"
#include "ui/views/bubble/bubble_dialog_model_host.h"
#include "ui/views/controls/button/label_button.h"
#include "ui/views/controls/webview/webview.h"
#include "ui/views/focus/focus_manager.h"
#include "ui/views/test/views_test_utils.h"
#include "ui/views/test/widget_activation_waiter.h"
#include "ui/views/view_class_properties.h"
#include "ui/views/view_utils.h"
#include "url/url_constants.h"

#if defined(USE_AURA)
#include "ui/aura/client/focus_client.h"
#include "ui/views/widget/native_widget_aura.h"
#include "ui/wm/core/window_properties.h"
#endif  // USE_AURA

#if BUILDFLAG(IS_OZONE)
#include "ui/ozone/public/ozone_platform.h"
#endif

#if BUILDFLAG(IS_CHROMEOS)
#include "chrome/browser/ash/boca/on_task/on_task_locked_controller.h"
#include "chrome/browser/ui/ash/test_util.h"
#endif

#if BUILDFLAG(IS_WIN)

class BrowserViewPipTest : public InProcessBrowserTest {
 public:
  BrowserViewPipTest() {
    feature_list_.InitAndEnableFeature(features::kExcludePipFromScreenCapture);
  }

 protected:
  base::test::ScopedFeatureList feature_list_;
};

IN_PROC_BROWSER_TEST_F(BrowserViewPipTest, DynamicStatePropagation) {
  content::ScopedPipExclusionOverride exclusion_override(false);

  // Create a PIP browser.
  BrowserWindowCreateParams params(
      BrowserWindowInterface::TYPE_PICTURE_IN_PICTURE, browser()->GetProfile(),
      /*from_user_gesture=*/true);
  BrowserWindowInterface* pip_browser = CreateBrowserWindow(std::move(params));
  pip_browser->GetWindow()->Show();
  BrowserView* pip_browser_view =
      BrowserView::GetBrowserViewForBrowser(pip_browser);

  // Use the override to flip the global state.
  exclusion_override.SetExcluded(true);
  EXPECT_TRUE(pip_browser_view->GetWidget()->GetNativeView()->GetProperty(
      wm::kExcludeFromScreenCaptureKey));

  exclusion_override.SetExcluded(false);
  EXPECT_FALSE(pip_browser_view->GetWidget()->GetNativeView()->GetProperty(
      wm::kExcludeFromScreenCaptureKey));

  CloseBrowserSynchronously(pip_browser);
}

IN_PROC_BROWSER_TEST_F(BrowserViewPipTest, InitialStateVerification) {
  // Mock coordinator to return true for exclusion.
  content::ScopedPipExclusionOverride exclusion_override(true);

  // Create a PIP browser.
  BrowserWindowCreateParams params(
      BrowserWindowInterface::TYPE_PICTURE_IN_PICTURE, browser()->GetProfile(),
      /*from_user_gesture=*/true);
  BrowserWindowInterface* pip_browser = CreateBrowserWindow(std::move(params));
  pip_browser->GetWindow()->Show();
  BrowserView* pip_browser_view =
      BrowserView::GetBrowserViewForBrowser(pip_browser);

  // Verify it's excluded immediately.
  EXPECT_TRUE(pip_browser_view->GetWidget()->GetNativeView()->GetProperty(
      wm::kExcludeFromScreenCaptureKey));

  CloseBrowserSynchronously(pip_browser);
}

class BrowserViewPipDisabledTest : public InProcessBrowserTest {
 public:
  BrowserViewPipDisabledTest() {
    feature_list_.InitAndDisableFeature(features::kExcludePipFromScreenCapture);
  }

 protected:
  base::test::ScopedFeatureList feature_list_;
};

IN_PROC_BROWSER_TEST_F(BrowserViewPipDisabledTest, FeatureFlagEnforcement) {
  content::ScopedPipExclusionOverride exclusion_override(false);

  // Create a PIP browser.
  BrowserWindowCreateParams params(
      BrowserWindowInterface::TYPE_PICTURE_IN_PICTURE, browser()->GetProfile(),
      /*from_user_gesture=*/true);
  BrowserWindowInterface* pip_browser = CreateBrowserWindow(std::move(params));
  pip_browser->GetWindow()->Show();
  BrowserView* pip_browser_view =
      BrowserView::GetBrowserViewForBrowser(pip_browser);

  // Flip the global state.
  exclusion_override.SetExcluded(true);

  // Property should still be false because of the gate.
  EXPECT_FALSE(pip_browser_view->GetWidget()->GetNativeView()->GetProperty(
      wm::kExcludeFromScreenCaptureKey));

  CloseBrowserSynchronously(pip_browser);
}

IN_PROC_BROWSER_TEST_F(BrowserViewPipTest, WindowTypeIsolation) {
  content::ScopedPipExclusionOverride exclusion_override(false);

  // Normal browser (already exists as browser()).
  BrowserView* normal_view = BrowserView::GetBrowserViewForBrowser(browser());

  // Flip the global state.
  exclusion_override.SetExcluded(true);

  // Property should remain false.
  EXPECT_FALSE(normal_view->GetWidget()->GetNativeView()->GetProperty(
      wm::kExcludeFromScreenCaptureKey));
}

IN_PROC_BROWSER_TEST_F(BrowserViewPipTest, NewWindowInitializationIsolation) {
  // Mock coordinator to return true for exclusion.
  content::ScopedPipExclusionOverride exclusion_override(true);

  // Create a normal browser.
  BrowserWindowCreateParams params(BrowserWindowInterface::TYPE_NORMAL,
                                   browser()->GetProfile(),
                                   /*from_user_gesture=*/true);
  BrowserWindowInterface* new_normal_browser =
      CreateBrowserWindow(std::move(params));
  new_normal_browser->GetWindow()->Show();

  BrowserView* new_normal_view =
      BrowserView::GetBrowserViewForBrowser(new_normal_browser);

  // Property should remain false because normal windows are not excluded.
  EXPECT_FALSE(new_normal_view->GetWidget()->GetNativeView()->GetProperty(
      wm::kExcludeFromScreenCaptureKey));

  CloseBrowserSynchronously(new_normal_browser);
}

#endif  // BUILDFLAG(IS_WIN)

class BrowserViewTest : public InProcessBrowserTest {
 public:
  BrowserViewTest() : devtools_(nullptr) {}

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

 protected:
  Profile* profile() { return browser()->GetProfile(); }

  BrowserView* browser_view() {
    return BrowserView::GetBrowserViewForBrowser(browser());
  }

  views::WebView* devtools_web_view() {
    return browser_view()
        ->GetActiveContentsContainerView()
        ->devtools_web_view();
  }

  views::WebView* contents_web_view() {
    return browser_view()->contents_web_view();
  }

  content::WebContents* active_web_contents() {
    return browser()->GetTabStripModel()->GetActiveWebContents();
  }

  ScrimView* active_contents_scrim_view() {
    return browser_view()
        ->GetActiveContentsContainerView()
        ->contents_scrim_view();
  }

  ContentsContainerView* active_contents_container_view() {
    return browser_view()
        ->multi_contents_view()
        ->GetActiveContentsContainerView();
  }

  ContentsContainerView* inactive_contents_container_view() {
    return browser_view()
        ->multi_contents_view()
        ->GetInactiveContentsContainerView();
  }

  std::u16string GetAccessibleNameForTabAt(int index) {
    return browser_view()
        ->tab_strip_view()
        ->GetTabAnchorView(
            browser()->GetTabStripModel()->GetTabAtIndex(index)->GetHandle())
        ->GetViewAccessibility()
        .GetCachedName();
  }

  void OpenDevToolsWindow(bool docked) {
    devtools_ =
        DevToolsWindowTesting::OpenDevToolsWindowSync(browser(), docked);
  }

  void CloseDevToolsWindow() {
    DevToolsWindowTesting::CloseDevToolsWindowSync(
        devtools_.ExtractAsDangling());
  }

  void SetDevToolsBounds(const gfx::Rect& bounds) {
    DevToolsWindowTesting::Get(devtools_)->SetInspectedPageBounds(bounds);
  }

  void AddTab(Browser* browser, const GURL& url) {
    chrome::AddTabAt(browser, url, /*index=*/-1, /*foreground=*/true);
  }

  raw_ptr<DevToolsWindow> devtools_;
};

#if BUILDFLAG(IS_CHROMEOS)
using BrowserViewChromeOSTest = ChromeOSBrowserUITest;
#endif

namespace {
// Used to simulate scenario in a crash. When WebContentsDestroyed() is
// invoked updates the navigation state of another tab.
class TestWebContentsObserver : public content::WebContentsObserver {
 public:
  TestWebContentsObserver(content::WebContents* source,
                          content::WebContents* other)
      : content::WebContentsObserver(source), other_(other) {}

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

  ~TestWebContentsObserver() override = default;

  void WebContentsDestroyed() override {
    other_->NotifyNavigationStateChanged(static_cast<content::InvalidateTypes>(
        content::INVALIDATE_TYPE_URL | content::INVALIDATE_TYPE_LOAD));
  }

 private:
  raw_ptr<content::WebContents, DanglingUntriaged> other_;
};

// Waits for a different view to claim focus within a widget with the
// specified name.
class TestFocusChangeWaiter : public views::FocusChangeListener {
 public:
  TestFocusChangeWaiter(views::FocusManager* focus_manager,
                        const std::string& expected_widget_name)
      : focus_manager_(focus_manager),
        expected_widget_name_(expected_widget_name) {
    if (auto* current_focused_view = focus_manager->GetFocusedView()) {
      previous_view_id_ = current_focused_view->GetID();
    } else {
      previous_view_id_ = -1;
    }
    focus_manager_->AddFocusChangeListener(this);
  }

  TestFocusChangeWaiter(const TestFocusChangeWaiter&) = delete;
  TestFocusChangeWaiter& operator=(const TestFocusChangeWaiter&) = delete;
  ~TestFocusChangeWaiter() override {
    focus_manager_->RemoveFocusChangeListener(this);
  }

  void Wait() { run_loop_.Run(); }

 private:
  // views::FocusChangeListener:
  void OnDidChangeFocus(views::View* focused_before,
                        views::View* focused_now) override {
    if (focused_now && focused_now->GetID() != previous_view_id_) {
      views::Widget* widget = focused_now->GetWidget();
      if (widget && widget->GetName() == expected_widget_name_) {
        run_loop_.Quit();
      }
    }
  }

  raw_ptr<views::FocusManager> focus_manager_;
  base::RunLoop run_loop_;
  int previous_view_id_;
  std::string expected_widget_name_;
  base::WeakPtrFactory<TestFocusChangeWaiter> weak_factory_{this};
};

class TestTabModalConfirmDialogDelegate : public TabModalConfirmDialogDelegate {
 public:
  explicit TestTabModalConfirmDialogDelegate(content::WebContents* contents)
      : TabModalConfirmDialogDelegate(contents) {}

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

  std::u16string GetTitle() override { return std::u16string(u"Dialog Title"); }
  std::u16string GetDialogMessage() override { return std::u16string(); }
};
}  // namespace

// Verifies don't crash when CloseNow() is invoked with two tabs in a browser.
// Additionally when one of the tabs is destroyed NotifyNavigationStateChanged()
// is invoked on the other.
IN_PROC_BROWSER_TEST_F(BrowserViewTest, CloseWithTabs) {
  BrowserWindowInterface* browser2 = CreateBrowserWindow(
      BrowserWindowCreateParams(browser()->GetProfile(),
                                /*from_user_gesture=*/true));
  chrome::AddTabAt(browser2, GURL(), -1, true);
  chrome::AddTabAt(browser2, GURL(), -1, true);
  TestWebContentsObserver observer(
      browser2->GetTabStripModel()->GetWebContentsAt(0),
      browser2->GetTabStripModel()->GetWebContentsAt(1));
  BrowserView::GetBrowserViewForBrowser(browser2)->GetWidget()->CloseNow();
}

// Same as CloseWithTabs, but activates the first tab, which is the first tab
// BrowserView will destroy.
IN_PROC_BROWSER_TEST_F(BrowserViewTest, CloseWithTabsStartWithActive) {
  BrowserWindowInterface* browser2 = CreateBrowserWindow(
      BrowserWindowCreateParams(browser()->GetProfile(),
                                /*from_user_gesture=*/true));
  chrome::AddTabAt(browser2, GURL(), -1, true);
  chrome::AddTabAt(browser2, GURL(), -1, true);
  browser2->GetTabStripModel()->ActivateTabAt(
      0, TabStripUserGestureDetails(
             TabStripUserGestureDetails::GestureType::kOther));
  TestWebContentsObserver observer(
      browser2->GetTabStripModel()->GetWebContentsAt(0),
      browser2->GetTabStripModel()->GetWebContentsAt(1));
  BrowserView::GetBrowserViewForBrowser(browser2)->GetWidget()->CloseNow();
}

#if BUILDFLAG(IS_CHROMEOS)
IN_PROC_BROWSER_TEST_F(BrowserViewTest, OnTaskLockedBrowserView) {
  ash::boca::OnTaskLockedController::From(browser())->set_locked_for_on_task(
      true);
  EXPECT_FALSE(browser_view()->CanMinimize());
  EXPECT_FALSE(browser_view()->ShouldShowCloseButton());
}

IN_PROC_BROWSER_TEST_F(BrowserViewTest, OnTaskUnlockedBrowserView) {
  ash::boca::OnTaskLockedController::From(browser())->set_locked_for_on_task(
      false);
  EXPECT_TRUE(browser_view()->CanMinimize());
  EXPECT_TRUE(browser_view()->ShouldShowCloseButton());
}
#endif

// Verifies that page and devtools WebViews are being correctly laid out
// when DevTools is opened/closed/updated while docked.
IN_PROC_BROWSER_TEST_F(BrowserViewTest, DevToolsDockedUpdatesBrowserWindow) {
#if BUILDFLAG(IS_OZONE)
  // Ozone/wayland doesn't support getting/setting window position in global
  // screen coordinates. So this test is not applicable.
  if (ui::OzonePlatform::RunningOnWaylandForTest()) {
    GTEST_SKIP();
  }
#endif
  gfx::Rect full_bounds = active_contents_container_view()->GetLocalBounds();
  gfx::Rect small_bounds(10, 20, 30, 40);

  DevtoolsUIController::From(browser())->UpdateDevtools(active_web_contents());
  views::test::RunScheduledLayout(browser_view());
  EXPECT_FALSE(devtools_web_view()->web_contents());
  EXPECT_EQ(full_bounds, devtools_web_view()->bounds());
  EXPECT_EQ(full_bounds, contents_web_view()->bounds());

  // Docked.
  OpenDevToolsWindow(true);
  views::test::RunScheduledLayout(browser_view());
  EXPECT_TRUE(devtools_web_view()->web_contents());
  EXPECT_EQ(full_bounds, devtools_web_view()->bounds());

  SetDevToolsBounds(small_bounds);
  views::test::RunScheduledLayout(browser_view());
  EXPECT_TRUE(devtools_web_view()->web_contents());
  EXPECT_EQ(full_bounds, devtools_web_view()->bounds());
  EXPECT_EQ(small_bounds, contents_web_view()->bounds());

  DevtoolsUIController::From(browser())->UpdateDevtools(active_web_contents());
  views::test::RunScheduledLayout(browser_view());
  EXPECT_TRUE(devtools_web_view()->web_contents());
  EXPECT_EQ(full_bounds, devtools_web_view()->bounds());
  EXPECT_EQ(small_bounds, contents_web_view()->bounds());

  CloseDevToolsWindow();
  views::test::RunScheduledLayout(browser_view());
  EXPECT_FALSE(devtools_web_view()->web_contents());
  EXPECT_EQ(full_bounds, devtools_web_view()->bounds());
  EXPECT_EQ(full_bounds, contents_web_view()->bounds());

  DevtoolsUIController::From(browser())->UpdateDevtools(active_web_contents());
  views::test::RunScheduledLayout(browser_view());
  EXPECT_FALSE(devtools_web_view()->web_contents());
  EXPECT_EQ(full_bounds, devtools_web_view()->bounds());
  EXPECT_EQ(full_bounds, contents_web_view()->bounds());
}

// Verifies that page and devtools WebViews are being correctly laid out
// when DevTools is opened/closed/updated while undocked.
IN_PROC_BROWSER_TEST_F(BrowserViewTest, DevToolsUndockedUpdatesBrowserWindow) {
#if BUILDFLAG(IS_OZONE)
  // Ozone/wayland doesn't support getting/setting window position in global
  // screen coordinates. So this test is not applicable.
  if (ui::OzonePlatform::RunningOnWaylandForTest()) {
    GTEST_SKIP();
  }
#endif
  gfx::Rect full_bounds = active_contents_container_view()->GetLocalBounds();
  gfx::Rect small_bounds(10, 20, 30, 40);

  OpenDevToolsWindow(false);
  views::test::RunScheduledLayout(browser_view());
  EXPECT_TRUE(devtools_web_view()->web_contents());
  EXPECT_EQ(full_bounds, devtools_web_view()->bounds());

  SetDevToolsBounds(small_bounds);
  views::test::RunScheduledLayout(browser_view());
  EXPECT_TRUE(devtools_web_view()->web_contents());
  EXPECT_EQ(full_bounds, devtools_web_view()->bounds());
  EXPECT_EQ(small_bounds, contents_web_view()->bounds());

  DevtoolsUIController::From(browser())->UpdateDevtools(active_web_contents());
  views::test::RunScheduledLayout(browser_view());
  EXPECT_TRUE(devtools_web_view()->web_contents());
  EXPECT_EQ(full_bounds, devtools_web_view()->bounds());
  EXPECT_EQ(small_bounds, contents_web_view()->bounds());

  CloseDevToolsWindow();
  views::test::RunScheduledLayout(browser_view());
  EXPECT_FALSE(devtools_web_view()->web_contents());
  EXPECT_EQ(full_bounds, devtools_web_view()->bounds());
  EXPECT_EQ(full_bounds, contents_web_view()->bounds());

  DevtoolsUIController::From(browser())->UpdateDevtools(active_web_contents());
  views::test::RunScheduledLayout(browser_view());
  EXPECT_FALSE(devtools_web_view()->web_contents());
  EXPECT_EQ(full_bounds, devtools_web_view()->bounds());
  EXPECT_EQ(full_bounds, contents_web_view()->bounds());
}

void SetDevToolsWindowSizePrefs(BrowserWindowInterface* browser,
                                int left,
                                int right,
                                int top,
                                int bottom) {
  PrefService* prefs = browser->GetProfile()->GetPrefs();
  ScopedDictPrefUpdate update(prefs, prefs::kAppWindowPlacement);
  base::DictValue& wp_prefs = update.Get();
  base::DictValue dev_tools_defaults;
  dev_tools_defaults.Set("left", left);
  dev_tools_defaults.Set("right", right);
  dev_tools_defaults.Set("top", top);
  dev_tools_defaults.Set("bottom", bottom);
  dev_tools_defaults.Set("maximized", false);
  dev_tools_defaults.Set("always_on_top", false);
  wp_prefs.Set(DevToolsWindow::kDevToolsApp, std::move(dev_tools_defaults));
}

const base::DictValue& GetDevToolsWindowSizePrefs(
    BrowserWindowInterface* browser) {
  PrefService* prefs = browser->GetProfile()->GetPrefs();
  return prefs->GetDict(prefs::kAppWindowPlacement)
      .Find(DevToolsWindow::kDevToolsApp)
      ->GetDict();
}

auto HasDimensions(int left, int right, int top, int bottom) {
  return base::test::DictionaryHasValues(base::DictValue()
                                             .Set("left", left)
                                             .Set("right", right)
                                             .Set("top", top)
                                             .Set("bottom", bottom));
}

// Test basic construction and initialization.
IN_PROC_BROWSER_TEST_F(BrowserViewTest, BrowserView) {
  views::test::RunScheduledLayout(browser_view());

  // Test initial state.
  EXPECT_TRUE(browser_view()->GetTabStripVisible());
  EXPECT_FALSE(browser_view()->GetIncognito());
  EXPECT_FALSE(browser_view()->GetGuestSession());
  EXPECT_TRUE(browser_view()->GetIsNormalType());
  EXPECT_FALSE(browser_view()->IsFullscreen());
  EXPECT_FALSE(browser_view()->IsBookmarkBarVisible());
  EXPECT_FALSE(browser_view()->IsBookmarkBarAnimating());

  // Test action item creation.
  BrowserActions* browser_actions = BrowserActions::From(browser());

  ASSERT_TRUE(browser_actions->root_action_item());
  EXPECT_GE(
      browser_actions->root_action_item()->GetChildren().children().size(),
      1UL);

  actions::ActionItemVector actions;
  auto& manager = actions::ActionManager::GetForTesting();
  manager.GetActions(actions);

  actions::ActionItem* customize_chrome_action = manager.FindAction(
      kActionSidePanelShowCustomizeChrome, browser_actions->root_action_item());
  ASSERT_TRUE(customize_chrome_action);
  EXPECT_EQ(customize_chrome_action->GetText(),
            l10n_util::GetStringUTF16(IDS_SIDE_PANEL_CUSTOMIZE_CHROME_TITLE));
  EXPECT_EQ(customize_chrome_action->GetImage(),
            ui::ImageModel::FromVectorIcon(
                features::IsRoundedIconsEnabled()
                    ? kEditIcon
                    : vector_icons::kEditChromeRefreshOldIcon,
                ui::kColorIcon));
  EXPECT_TRUE(customize_chrome_action->GetEnabled());
}

// Test the find bar's bounding box when the location bar is visible.
IN_PROC_BROWSER_TEST_F(BrowserViewTest, FindBarBoundingBoxLocationBar) {
  BrowserView* browser_view = BrowserView::GetBrowserViewForBrowser(browser());
  views::test::RunScheduledLayout(browser_view);

  gfx::Rect find_bar_bounds = browser_view->GetFindBarBoundingBox();
  EXPECT_FALSE(find_bar_bounds.IsEmpty());
  EXPECT_GT(find_bar_bounds.width(), 0);
  EXPECT_GT(find_bar_bounds.height(), 0);
}

// Test the find bar's bounding box when the location bar is not visible.
IN_PROC_BROWSER_TEST_F(BrowserViewTest, FindBarBoundingBoxNoLocationBar) {
  BrowserWindowCreateParams params(BrowserWindowInterface::TYPE_POPUP,
                                   browser()->GetProfile(),
                                   /*from_user_gesture=*/true);
  BrowserWindowInterface* popup_browser =
      CreateBrowserWindow(std::move(params));
  chrome::AddTabAt(popup_browser, GURL("about:blank"), -1, true);
  popup_browser->GetWindow()->Show();

  BrowserView* popup_browser_view =
      BrowserView::GetBrowserViewForBrowser(popup_browser);
  views::test::RunScheduledLayout(popup_browser_view);

  // When location bar is not visible (e.g. in popups or app windows),
  // find bar should still return a valid non-empty bounding box.
  gfx::Rect bounds = popup_browser_view->GetFindBarBoundingBox();
  EXPECT_GT(bounds.width(), 0);
  EXPECT_GT(bounds.height(), 0);

  CloseBrowserSynchronously(popup_browser);
}

// Browser widget must be visible for ui::ElementIdentifiers to resolve.
IN_PROC_BROWSER_TEST_F(BrowserViewTest, RotatePaneFocusFromView) {
  BrowserView* browser_view = BrowserView::GetBrowserViewForBrowser(browser());
  browser_view->GetWidget()->Activate();
  // Native NSWindow widget activation events are not reliably dispatched on
  // headless macOS CI bots without a physical window server, causing activation
  // timeouts. Focus rotation logic can still be tested via views::FocusManager.
#if !BUILDFLAG(IS_MAC)
  views::test::WaitForWidgetActive(browser_view->GetWidget(), true);
#endif

  auto dialog_model = ui::DialogModel::Builder()
                          .SetTitle(u"test")
                          .SetIsAlertDialog()
                          .AddOkButton(base::DoNothing())
                          .Build();
  views::View* anchor = browser_view->GetLocationBarView();

  auto bubble = std::make_unique<views::BubbleDialogModelHost>(
      std::move(dialog_model), anchor, views::BubbleBorder::TOP_RIGHT);
  auto* bubble_ptr = bubble.get();
  views::Widget* widget = views::BubbleDialogDelegate::CreateBubbleDeprecated(
      std::move(bubble), views::Widget::InitParams::NATIVE_WIDGET_OWNS_WIDGET);
  widget->Show();

  // OK button cannot be retrieved until CreateBubble has been called.
  views::View* ok_button = bubble_ptr->GetOkButton();

  views::FocusManager* focus_manager = widget->GetFocusManager();
  focus_manager->SetKeyboardAccessible(true);

  // Initial rotation should return a "rotated" result.
  EXPECT_TRUE(browser_view->RotatePaneFocusFromView(nullptr, true, true));
  EXPECT_EQ(ok_button, focus_manager->GetStoredFocusView());

  // Next rotation should not return a "rotated" result and should not change
  // the focus.
  EXPECT_FALSE(browser_view->RotatePaneFocusFromView(nullptr, true, false));
  EXPECT_EQ(ok_button, focus_manager->GetStoredFocusView());
}

// Macs do not have fullscreen policy.
#if !BUILDFLAG(IS_MAC)

IN_PROC_BROWSER_TEST_F(BrowserViewTest, CanFullscreenPolicyWatcher) {
  BrowserView* browser_view = BrowserView::GetBrowserViewForBrowser(browser());
  EXPECT_TRUE(browser_view->CanFullscreen());

  browser()->GetProfile()->GetPrefs()->SetBoolean(prefs::kFullscreenAllowed,
                                                  false);
  EXPECT_FALSE(browser_view->CanFullscreen());

  browser()->GetProfile()->GetPrefs()->SetBoolean(prefs::kFullscreenAllowed,
                                                  true);
  EXPECT_TRUE(browser_view->CanFullscreen());
}

IN_PROC_BROWSER_TEST_F(BrowserViewTest,
                       CanFullscreenPolicyDoesNotEnableFullscreen) {
  BrowserWindowCreateParams params =
      BrowserWindowCreateParams::CreateForPictureInPicture(
          "PipApp", /*trusted_source=*/true, browser()->GetProfile(),
          /*user_gesture=*/true);
  BrowserWindowInterface* pip_browser = CreateBrowserWindow(std::move(params));
  pip_browser->GetWindow()->Show();
  BrowserView* pip_browser_view =
      BrowserView::GetBrowserViewForBrowser(pip_browser);

  // PIP windows cannot be fullscreened even if policy allows.
  browser()->GetProfile()->GetPrefs()->SetBoolean(prefs::kFullscreenAllowed,
                                                  true);
  EXPECT_FALSE(pip_browser_view->CanFullscreen());

  CloseBrowserSynchronously(pip_browser);
}

#endif  // !BUILDFLAG(IS_MAC)

class BrowserViewHostedAppTest : public InProcessBrowserTest {
 public:
  BrowserViewHostedAppTest() = default;
  BrowserViewHostedAppTest(const BrowserViewHostedAppTest&) = delete;
  BrowserViewHostedAppTest& operator=(const BrowserViewHostedAppTest&) = delete;
  ~BrowserViewHostedAppTest() override = default;
};

IN_PROC_BROWSER_TEST_F(BrowserViewHostedAppTest, Layout) {
  BrowserWindowCreateParams params = BrowserWindowCreateParams::CreateForApp(
      "Test", /*trusted_source=*/true, /*window_bounds=*/gfx::Rect(),
      browser()->GetProfile(), /*user_gesture=*/true);
  params.type = BrowserWindowInterface::TYPE_POPUP;
  BrowserWindowInterface* app_browser = CreateBrowserWindow(std::move(params));
  chrome::AddTabAt(app_browser, GURL("about:blank"), -1, true);
  app_browser->GetWindow()->Show();

  BrowserView* app_browser_view =
      BrowserView::GetBrowserViewForBrowser(app_browser);
  views::test::RunScheduledLayout(app_browser_view);

  // The tabstrip, toolbar and bookmark bar should not be visible for hosted
  // apps.
  EXPECT_FALSE(
      app_browser_view->horizontal_tab_strip_for_testing()->GetVisible());
  EXPECT_FALSE(app_browser_view->toolbar()->GetVisible());
  EXPECT_FALSE(app_browser_view->IsBookmarkBarVisible());

  const auto* const frame_view =
      app_browser_view->browser_widget()->GetFrameView();

  gfx::Point header_offset;
  views::View::ConvertPointToTarget(app_browser_view, frame_view,
                                    &header_offset);

  const auto layout_params = frame_view->GetBrowserLayoutParams();

  const int contents_container_y = app_browser_view->contents_container()->y();

#if BUILDFLAG(IS_MAC)
  // The system paints the caption area, so the contents start at the top of
  // the layout.
  const int bottom_of_header = 0;
#else
  // The position of the bottom of the header (the bar with the window
  // controls) in the coordinates of the browser view.
  const int bottom_of_header = base::ClampCeil(
      std::max(layout_params.leading_exclusion.ContentWithPadding().height(),
               layout_params.trailing_exclusion.ContentWithPadding().height()));
#endif

  // The top of the browser view in the coordinates of the frame.
  const int top_inset = bottom_of_header + layout_params.visual_client_area.y();

  // The web contents should be flush with the bottom of the header.
  EXPECT_EQ(bottom_of_header, contents_container_y);

  // The find bar should be aligned with the bottom of the header in the
  // coordinates of the frame.
  EXPECT_EQ(top_inset, app_browser_view->GetFindBarBoundingBox().y());

  CloseBrowserSynchronously(app_browser);
}

// Tests that a browser window is correctly associated to a WebContents that
// belongs to that window's UI hierarchy.
IN_PROC_BROWSER_TEST_F(BrowserViewTest, FindBrowserWindowWithWebContents) {
  auto web_view = std::make_unique<views::WebView>(browser()->GetProfile());
  ASSERT_NE(nullptr, web_view->GetWebContents());

  // If the web contents does not belong browser's UI hierarchy there should not
  // be any associated browser window.
  EXPECT_EQ(nullptr, BrowserWindow::FindBrowserWindowWithWebContents(
                         web_view->GetWebContents()));

  // After adding the web contents to the browser's UI hierarchy the browser
  // window should be correctly associated with the contents.
  auto* web_view_ptr = browser_view()->AddChildView(std::move(web_view));
  EXPECT_EQ(browser_view(), BrowserWindow::FindBrowserWindowWithWebContents(
                                web_view_ptr->GetWebContents()));

  // Removing the web contents from the browser's UI hierarchy should
  // disassociate it with the browser window.
  web_view = browser_view()->RemoveChildViewT(web_view_ptr);
  EXPECT_EQ(nullptr, BrowserWindow::FindBrowserWindowWithWebContents(
                         web_view->GetWebContents()));
}

// Tests that tab contents are correctly associated with their browser window,
// even when non-active.
IN_PROC_BROWSER_TEST_F(BrowserViewTest,
                       FindBrowserWindowWithWebContentsTabSwitch) {
  content::WebContents* original_active_contents =
      browser_view()->GetActiveWebContents();
  EXPECT_EQ(browser_view(), BrowserWindow::FindBrowserWindowWithWebContents(
                                original_active_contents));

  // Inactive tabs (aka tabs with their web contents not currently embedded in
  // the browser's ContentWebView) should still be associated with their hosting
  // browser window.
  AddTab(browser(), GURL("about:blank"));
  content::WebContents* new_active_contents =
      browser_view()->GetActiveWebContents();
  EXPECT_NE(original_active_contents, browser_view()->GetActiveWebContents());
  EXPECT_EQ(new_active_contents, browser_view()->GetActiveWebContents());
  EXPECT_EQ(browser_view(), BrowserWindow::FindBrowserWindowWithWebContents(
                                original_active_contents));
  EXPECT_EQ(browser_view(), BrowserWindow::FindBrowserWindowWithWebContents(
                                new_active_contents));
}

// Tests that BrowserWindow::FromBrowser() resolves to the BrowserView-backed
// BrowserWindow, and handles edge cases.
IN_PROC_BROWSER_TEST_F(BrowserViewTest, FromBrowser) {
  // For a fully-constructed BrowserView-backed Browser the result must match
  // the BrowserView-specific lookup.
  EXPECT_EQ(browser_view(), BrowserWindow::FromBrowser(browser()));

  // Null input is tolerated and yields null output, mirroring the behavior
  // callers previously got from a defensive 'browser ? browser->window() :
  // nullptr' pattern. Cast disambiguates between the const/non-const overloads.
  EXPECT_EQ(nullptr, BrowserWindow::FromBrowser(
                         static_cast<BrowserWindowInterface*>(nullptr)));
}

#if !BUILDFLAG(IS_MAC)
// Test that calling `BrowserView::Activate()` or `BrowserView::Show()` sets
// the last active browser. Multi-window activation cannot be tested on headless
// macOS CI bots without a window server.
IN_PROC_BROWSER_TEST_F(BrowserViewTest, UpdateActiveBrowser) {
#if BUILDFLAG(IS_OZONE)
  if (ui::OzonePlatform::RunningOnWaylandForTest()) {
    GTEST_SKIP();
  }
#endif
  // Create browser2 without showing it first, so browser() remains the active
  // window.
  BrowserWindowInterface* browser2 = CreateBrowserWindow(
      BrowserWindowCreateParams(profile(), /*from_user_gesture=*/true));
  EXPECT_EQ(2u, GlobalBrowserCollection::GetInstance()->GetSize());
  EXPECT_EQ(browser(), GetLastActiveBrowserWindowInterfaceWithAnyProfile());

  browser2->GetWindow()->Show();
  ui_test_utils::WaitForBrowserSetLastActive(browser2);
  EXPECT_EQ(browser2, GetLastActiveBrowserWindowInterfaceWithAnyProfile());

  browser()->GetWindow()->Activate();
  ui_test_utils::WaitForBrowserSetLastActive(browser());
  EXPECT_EQ(browser(), GetLastActiveBrowserWindowInterfaceWithAnyProfile());

  browser2->GetWindow()->Activate();
  ui_test_utils::WaitForBrowserSetLastActive(browser2);
  EXPECT_EQ(browser2, GetLastActiveBrowserWindowInterfaceWithAnyProfile());

  browser()->GetWindow()->Activate();
  ui_test_utils::WaitForBrowserSetLastActive(browser());
  EXPECT_EQ(browser(), GetLastActiveBrowserWindowInterfaceWithAnyProfile());

  CloseBrowserSynchronously(browser2);
}

IN_PROC_BROWSER_TEST_F(BrowserViewTest, RecordShortcutMetrics) {
  base::HistogramTester histogram_tester;

  const ui::Accelerator kLocationAccel(ui::VKEY_L, ui::EF_PLATFORM_ACCELERATOR);
  EXPECT_TRUE(browser_view()->AcceleratorPressed(kLocationAccel));
  histogram_tester.ExpectUniqueSample("Browser.Shortcuts.TriggeredCommandId",
                                      IDC_FOCUS_LOCATION, 1);

  // Repeated key events should be excluded from telemetry.
  const ui::Accelerator kLocationRepeatAccel(
      ui::VKEY_L, ui::EF_PLATFORM_ACCELERATOR | ui::EF_IS_REPEAT);
  browser_view()->AcceleratorPressed(kLocationRepeatAccel);
  histogram_tester.ExpectUniqueSample("Browser.Shortcuts.TriggeredCommandId",
                                      IDC_FOCUS_LOCATION, 1);
}

// Test that repeated accelerators are processed or ignored depending on the
// commands that they refer to. The behavior for different commands is dictated
// by IsCommandRepeatable() in chrome/browser/ui/accelerator_table.h.
IN_PROC_BROWSER_TEST_F(BrowserViewTest, RepeatedAccelerators) {
  // A non-repeated Ctrl-L accelerator should be processed.
  const ui::Accelerator kLocationAccel(ui::VKEY_L, ui::EF_PLATFORM_ACCELERATOR);
  EXPECT_TRUE(browser_view()->AcceleratorPressed(kLocationAccel));

  // If the accelerator is repeated, it should be ignored.
  const ui::Accelerator kLocationRepeatAccel(
      ui::VKEY_L, ui::EF_PLATFORM_ACCELERATOR | ui::EF_IS_REPEAT);
  EXPECT_FALSE(browser_view()->AcceleratorPressed(kLocationRepeatAccel));

  // A repeated Ctrl-Tab accelerator should be processed.
  const ui::Accelerator kNextTabRepeatAccel(
      ui::VKEY_TAB, ui::EF_CONTROL_DOWN | ui::EF_IS_REPEAT);
  EXPECT_TRUE(browser_view()->AcceleratorPressed(kNextTabRepeatAccel));
}
#endif  // !BUILDFLAG(IS_MAC)

IN_PROC_BROWSER_TEST_F(BrowserViewTest, UpdateWindowTitle) {
  AddTab(browser(), GURL("about:blank"));
  AddTab(browser(), GURL("about:blank"));

  // Set and verify the first window user title.
  std::string user_title1 = "Test Title";
  WindowMetadataController::From(browser())->SetWindowUserTitle(user_title1);
  auto window_title = browser_view()->GetAccessibleWindowTitle();
  EXPECT_EQ(base::UTF8ToUTF16(user_title1),
            window_title.substr(0, user_title1.size()));

  // Set and verify the second window user title.
  std::string user_title2 = "Test Title 2";
  WindowMetadataController::From(browser())->SetWindowUserTitle(user_title2);
  window_title = browser_view()->GetAccessibleWindowTitle();
  EXPECT_EQ(base::UTF8ToUTF16(user_title2),
            window_title.substr(0, user_title2.size()));
}

IN_PROC_BROWSER_TEST_F(BrowserViewTest, WindowTitleOmitsLowMemoryUsage) {
  scoped_refptr<TabResourceUsage> tab_resource_usage =
      base::MakeRefCounted<TabResourceUsage>();
  tab_resource_usage->SetMemoryUsage(base::ByteSize(100));

  tabs::TabData memory_usage;
  memory_usage.tab_resource_usage = tab_resource_usage;

  Tab* const tab =
      browser_view()->horizontal_tab_strip_for_testing()->tab_at(0);
  tab->SetDataForTesting(std::move(memory_usage));

  // Expect that low memory usage isn't in the accessible window title.
  EXPECT_FALSE(browser_view()->GetAccessibleWindowTitle().contains(
      u"High memory usage"));

  base::ByteSize memory_used =
      TabResourceUsage::kHighMemoryUsageThreshold + base::ByteSize(1);
  tab_resource_usage->SetMemoryUsage(memory_used);

  // Expect that high memory usage is in the accessible window title.
  EXPECT_TRUE(browser_view()->GetAccessibleWindowTitle().contains(
      u"High memory usage"));
}

#if BUILDFLAG(IS_MAC)
// Tests that audio playing state is reflected in the "Window" menu on Mac.
IN_PROC_BROWSER_TEST_F(BrowserViewTest, TitleAudioIndicators) {
  std::u16string playing_icon = u"🔊";
  std::u16string muted_icon = u"🔇";

  AddTab(browser(), GURL("about:blank"));
  content::WebContents* contents = browser_view()->GetActiveWebContents();
  RecentlyAudibleHelper* audible_helper =
      RecentlyAudibleHelper::FromWebContents(contents);

  audible_helper->SetNotRecentlyAudibleForTesting();
  EXPECT_FALSE(browser_view()->GetWindowTitle().contains(playing_icon));
  EXPECT_FALSE(browser_view()->GetWindowTitle().contains(muted_icon));

  audible_helper->SetCurrentlyAudibleForTesting();
  EXPECT_TRUE(browser_view()->GetWindowTitle().contains(playing_icon));
  EXPECT_FALSE(browser_view()->GetWindowTitle().contains(muted_icon));

  audible_helper->SetRecentlyAudibleForTesting();
  contents->SetAudioMuted(true);
  EXPECT_FALSE(browser_view()->GetWindowTitle().contains(playing_icon));
  EXPECT_TRUE(browser_view()->GetWindowTitle().contains(muted_icon));
}
#endif

IN_PROC_BROWSER_TEST_F(BrowserViewTest, AccessibleProperties) {
  ui::AXNodeData data;

  browser_view()->GetViewAccessibility().GetAccessibleNodeData(&data);
  EXPECT_EQ(data.role, ax::mojom::Role::kClient);

  ui::AXNodeData root_view_data;
  browser_view()
      ->GetWidget()
      ->GetRootView()
      ->GetViewAccessibility()
      .GetAccessibleNodeData(&root_view_data);
  EXPECT_EQ(
      root_view_data.GetString16Attribute(ax::mojom::StringAttribute::kName),
      browser_view()->GetAccessibleWindowTitle());
}

IN_PROC_BROWSER_TEST_F(BrowserViewTest, UpdateAccessibleURL) {
  const GURL before_url("data:text/html,before");
  AddTab(browser(), before_url);

  ui::AXNodeData node_data;
  browser_view()
      ->GetWidget()
      ->GetRootView()
      ->GetViewAccessibility()
      .GetAccessibleNodeData(&node_data);
  EXPECT_EQ(node_data.GetStringAttribute(ax::mojom::StringAttribute::kUrl),
            before_url.spec());

  const GURL after_url("data:text/html,after");
  AddTab(browser(), after_url);
  browser_view()
      ->GetWidget()
      ->GetRootView()
      ->GetViewAccessibility()
      .GetAccessibleNodeData(&node_data);
  EXPECT_EQ(node_data.GetStringAttribute(ax::mojom::StringAttribute::kUrl),
            after_url.spec());
}

IN_PROC_BROWSER_TEST_F(BrowserViewTest, UpdateAccessibleURLOnTabSelection) {
  // Create two tabs with different URLs
  const GURL url1("data:text/html,tab1");
  const GURL url2("data:text/html,tab2");

  AddTab(browser(), url1);
  AddTab(browser(), url2);

  // Initially, the second tab should be active (most recently added)
  EXPECT_EQ(browser()->GetTabStripModel()->GetActiveWebContents()->GetURL(),
            url2);

  ui::AXNodeData node_data;
  browser_view()
      ->GetWidget()
      ->GetRootView()
      ->GetViewAccessibility()
      .GetAccessibleNodeData(&node_data);
  EXPECT_EQ(node_data.GetStringAttribute(ax::mojom::StringAttribute::kUrl),
            url2.spec());

  // Switch to the first tab (tab1 at index 1) by changing selection
  browser()->GetTabStripModel()->ActivateTabAt(1);
  EXPECT_EQ(browser()->GetTabStripModel()->GetActiveWebContents()->GetURL(),
            url1);

  // Verify that the accessible URL was updated due to tab selection change
  browser_view()
      ->GetWidget()
      ->GetRootView()
      ->GetViewAccessibility()
      .GetAccessibleNodeData(&node_data);
  EXPECT_EQ(node_data.GetStringAttribute(ax::mojom::StringAttribute::kUrl),
            url1.spec());

  // Switch back to the second tab (tab2 at index 2)
  browser()->GetTabStripModel()->ActivateTabAt(2);
  EXPECT_EQ(browser()->GetTabStripModel()->GetActiveWebContents()->GetURL(),
            url2);

  // Verify that the accessible URL was updated again
  browser_view()
      ->GetWidget()
      ->GetRootView()
      ->GetViewAccessibility()
      .GetAccessibleNodeData(&node_data);
  EXPECT_EQ(node_data.GetStringAttribute(ax::mojom::StringAttribute::kUrl),
            url2.spec());
}

// Tests Feature to ensure that the loading animation is not rendered after the
// window changes to hidden.
IN_PROC_BROWSER_TEST_F(BrowserViewTest,
                       LoadingAnimationNotRenderedWhenWindowHidden) {
  AddTab(browser(), GURL("about:blank"));
  content::WebContents* web_contents = browser_view()->GetActiveWebContents();

  auto navigation = content::NavigationSimulator::CreateBrowserInitiated(
      GURL("about:blank"), web_contents);
  navigation->SetKeepLoading(true);

  browser_view()->browser_widget()->Show();

  EXPECT_TRUE(browser()->GetTabStripModel()->TabsNeedLoadingUI());
  EXPECT_TRUE(browser_view()->IsLoadingAnimationRunning());

  browser_view()->browser_widget()->Hide();

  EXPECT_TRUE(browser()->GetTabStripModel()->TabsNeedLoadingUI());
  EXPECT_FALSE(browser_view()->IsLoadingAnimationRunning());
}

IN_PROC_BROWSER_TEST_F(BrowserViewTest, DevToolsWindowDefaultSize) {
#if BUILDFLAG(IS_OZONE)
  // Ozone/wayland doesn't support getting/setting window position in global
  // screen coordinates. So this test is not applicable.
  if (ui::OzonePlatform::RunningOnWaylandForTest()) {
    GTEST_SKIP();
  }
#endif
  // Starting DevTools the first time sets the window size to the default.
  OpenDevToolsWindow(false);
  CloseDevToolsWindow();
  EXPECT_THAT(GetDevToolsWindowSizePrefs(browser()),
              HasDimensions(100, 740, 100, 740));
}

IN_PROC_BROWSER_TEST_F(BrowserViewTest, DevToolsWindowKeepsSize) {
#if BUILDFLAG(IS_OZONE)
  // Ozone/wayland doesn't support getting/setting window position in global
  // screen coordinates. So this test is not applicable.
  if (ui::OzonePlatform::RunningOnWaylandForTest()) {
    GTEST_SKIP();
  }
#endif
  // Setting reasonable size prefs does not change the prefs.
  SetDevToolsWindowSizePrefs(browser(), 123, 567, 234, 678);
  EXPECT_THAT(GetDevToolsWindowSizePrefs(browser()),
              HasDimensions(123, 567, 234, 678));
  OpenDevToolsWindow(false);
  CloseDevToolsWindow();
  EXPECT_THAT(GetDevToolsWindowSizePrefs(browser()),
              HasDimensions(123, 567, 234, 678));
}

IN_PROC_BROWSER_TEST_F(BrowserViewTest, DevToolsWindowResetsSize) {
#if BUILDFLAG(IS_OZONE)
  // Ozone/wayland doesn't support getting/setting window position in global
  // screen coordinates. So this test is not applicable.
  if (ui::OzonePlatform::RunningOnWaylandForTest()) {
    GTEST_SKIP();
  }
#endif
  // Setting unreasonably small size prefs resets the prefs.
  SetDevToolsWindowSizePrefs(browser(), 121, 232, 343, 454);
  EXPECT_THAT(GetDevToolsWindowSizePrefs(browser()),
              HasDimensions(121, 232, 343, 454));
  OpenDevToolsWindow(false);
  CloseDevToolsWindow();
  EXPECT_THAT(GetDevToolsWindowSizePrefs(browser()),
              HasDimensions(100, 740, 100, 740));
}

class BookmarkBarViewObserverImpl : public BookmarkBarViewObserver {
 public:
  BookmarkBarViewObserverImpl() = default;

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

  int change_count() const { return change_count_; }
  void clear_change_count() { change_count_ = 0; }

  // BookmarkBarViewObserver:
  void OnBookmarkBarVisibilityChanged() override { change_count_++; }

 private:
  int change_count_ = 0;
};

class BrowserViewLegacyBookmarkBarTest : public BrowserViewTest {
 public:
  BrowserViewLegacyBookmarkBarTest() {
    feature_list_.InitAndDisableFeature(
        ntp_features::kNtpSimplificationBookmarkBar);
  }

 protected:
  base::test::ScopedFeatureList feature_list_;
};

// Verifies we don't unnecessarily change the visibility of the BookmarkBarView.
IN_PROC_BROWSER_TEST_F(BrowserViewLegacyBookmarkBarTest,
                       AvoidUnnecessaryVisibilityChanges) {
  // Create two tabs, the first empty and the second the ntp. Make it so the
  // BookmarkBarView isn't shown.
  browser()->GetProfile()->GetPrefs()->SetBoolean(
      bookmarks::prefs::kShowBookmarkBar, false);
  GURL new_tab_url = chrome::ChromeUINewTabURLAsGURL();
  chrome::AddTabAt(browser(), GURL(), -1, true);
  ASSERT_TRUE(ui_test_utils::NavigateToURL(browser(), new_tab_url));

  ASSERT_TRUE(browser_view()->bookmark_bar());
  BookmarkBarViewObserverImpl observer;
  BookmarkBarView* bookmark_bar = browser_view()->bookmark_bar();
  bookmark_bar->AddObserver(&observer);
  EXPECT_FALSE(bookmark_bar->GetVisible());

  // Go to empty tab. Bookmark bar should hide.
  browser()->GetTabStripModel()->ActivateTabAt(
      0, TabStripUserGestureDetails(
             TabStripUserGestureDetails::GestureType::kOther));
  EXPECT_FALSE(bookmark_bar->GetVisible());
  EXPECT_EQ(0, observer.change_count());
  observer.clear_change_count();

  // Go to ntp tab. Bookmark bar should not show.
  browser()->GetTabStripModel()->ActivateTabAt(
      1, TabStripUserGestureDetails(
             TabStripUserGestureDetails::GestureType::kOther));
  EXPECT_FALSE(bookmark_bar->GetVisible());
  EXPECT_EQ(0, observer.change_count());
  observer.clear_change_count();

  // Repeat with the bookmark bar always visible.
  browser()->GetProfile()->GetPrefs()->SetBoolean(
      bookmarks::prefs::kShowBookmarkBar, true);
  browser()->GetTabStripModel()->ActivateTabAt(
      0, TabStripUserGestureDetails(
             TabStripUserGestureDetails::GestureType::kOther));
  EXPECT_TRUE(bookmark_bar->GetVisible());
  EXPECT_EQ(1, observer.change_count());
  observer.clear_change_count();

  browser()->GetTabStripModel()->ActivateTabAt(
      1, TabStripUserGestureDetails(
             TabStripUserGestureDetails::GestureType::kOther));
  EXPECT_TRUE(bookmark_bar->GetVisible());
  EXPECT_EQ(0, observer.change_count());
  observer.clear_change_count();

  browser_view()->bookmark_bar()->RemoveObserver(&observer);
}

class BrowserViewSimplifiedBookmarkBarTest : public BrowserViewTest {
 public:
  BrowserViewSimplifiedBookmarkBarTest() {
    feature_list_.InitAndEnableFeature(
        ntp_features::kNtpSimplificationBookmarkBar);
  }

 protected:
  base::test::ScopedFeatureList feature_list_;
};

// Verifies we don't unnecessarily change the visibility of the BookmarkBarView
// when using the simplified bookmark bar feature.
IN_PROC_BROWSER_TEST_F(BrowserViewSimplifiedBookmarkBarTest,
                       AvoidUnnecessaryVisibilityChanges) {
  browser()->GetProfile()->GetPrefs()->SetInteger(
      bookmarks::prefs::kBookmarkBarVisibilityState,
      static_cast<int>(bookmarks::BookmarkBarVisibilityState::kOnlyShowOnNtp));
  GURL new_tab_url = chrome::ChromeUINewTabURLAsGURL();
  chrome::AddTabAt(browser(), GURL(), -1, true);
  ASSERT_TRUE(ui_test_utils::NavigateToURL(browser(), new_tab_url));

  ASSERT_TRUE(browser_view()->bookmark_bar());
  BookmarkBarViewObserverImpl observer;
  BookmarkBarView* bookmark_bar = browser_view()->bookmark_bar();
  bookmark_bar->AddObserver(&observer);
  EXPECT_FALSE(bookmark_bar->GetVisible());

  // Go to empty tab. Bookmark bar should hide.
  browser()->GetTabStripModel()->ActivateTabAt(
      0, TabStripUserGestureDetails(
             TabStripUserGestureDetails::GestureType::kOther));
  EXPECT_FALSE(bookmark_bar->GetVisible());
  EXPECT_EQ(0, observer.change_count());
  observer.clear_change_count();

  // Go to ntp tab. Bookmark bar should not show.
  browser()->GetTabStripModel()->ActivateTabAt(
      1, TabStripUserGestureDetails(
             TabStripUserGestureDetails::GestureType::kOther));
  EXPECT_FALSE(bookmark_bar->GetVisible());
  EXPECT_EQ(0, observer.change_count());
  observer.clear_change_count();

  // Repeat with the bookmark bar always visible.
  browser()->GetProfile()->GetPrefs()->SetInteger(
      bookmarks::prefs::kBookmarkBarVisibilityState,
      static_cast<int>(bookmarks::BookmarkBarVisibilityState::kAlwaysShow));
  browser()->GetTabStripModel()->ActivateTabAt(
      0, TabStripUserGestureDetails(
             TabStripUserGestureDetails::GestureType::kOther));
  EXPECT_TRUE(bookmark_bar->GetVisible());
  EXPECT_EQ(1, observer.change_count());
  observer.clear_change_count();

  browser()->GetTabStripModel()->ActivateTabAt(
      1, TabStripUserGestureDetails(
             TabStripUserGestureDetails::GestureType::kOther));
  EXPECT_TRUE(bookmark_bar->GetVisible());
  EXPECT_EQ(0, observer.change_count());
  observer.clear_change_count();

  // Repeat with the bookmark bar always hidden.
  browser()->GetProfile()->GetPrefs()->SetInteger(
      bookmarks::prefs::kBookmarkBarVisibilityState,
      static_cast<int>(bookmarks::BookmarkBarVisibilityState::kAlwaysHide));
  browser()->GetTabStripModel()->ActivateTabAt(
      0, TabStripUserGestureDetails(
             TabStripUserGestureDetails::GestureType::kOther));
  EXPECT_FALSE(bookmark_bar->GetVisible());
  EXPECT_EQ(1, observer.change_count());
  observer.clear_change_count();

  browser()->GetTabStripModel()->ActivateTabAt(
      1, TabStripUserGestureDetails(
             TabStripUserGestureDetails::GestureType::kOther));
  EXPECT_FALSE(bookmark_bar->GetVisible());
  EXPECT_EQ(0, observer.change_count());
  observer.clear_change_count();

  browser_view()->bookmark_bar()->RemoveObserver(&observer);
}

// Launch the app, navigate to a page with a title, check that the tab title
// is set before load finishes and the throbber state updates when the title
// changes. Regression test for crbug.com/40533493
IN_PROC_BROWSER_TEST_F(BrowserViewTest, TitleAndLoadState) {
  const std::u16string test_title(u"Title Of Awesomeness");
  auto* contents = browser()->GetTabStripModel()->GetActiveWebContents();
  content::TitleWatcher title_watcher(contents, test_title);
  content::TestNavigationObserver navigation_watcher(
      contents, 1, content::MessageLoopRunner::QuitMode::DEFERRED);

  TabStrip* tab_strip = browser_view()->horizontal_tab_strip_for_testing();
  // Navigate without blocking.
  const GURL test_url = chrome_test_utils::GetTestUrl(
      base::FilePath(base::FilePath::kCurrentDirectory),
      base::FilePath(FILE_PATH_LITERAL("title2.html")));
  contents->GetController().LoadURL(test_url, content::Referrer(),
                                    ui::PAGE_TRANSITION_LINK, std::string());
  EXPECT_TRUE(browser()->GetTabStripModel()->TabsNeedLoadingUI());
  EXPECT_EQ(tabs::TabNetworkState::kWaiting,
            tab_strip->tab_at(0)->data().network_state);
  EXPECT_EQ(test_title, title_watcher.WaitAndGetTitle());
  EXPECT_TRUE(browser()->GetTabStripModel()->TabsNeedLoadingUI());
  EXPECT_EQ(tabs::TabNetworkState::kLoading,
            tab_strip->tab_at(0)->data().network_state);

  // Now block for the navigation to complete.
  navigation_watcher.Wait();
  EXPECT_FALSE(browser()->GetTabStripModel()->TabsNeedLoadingUI());
  EXPECT_EQ(tabs::TabNetworkState::kNone,
            tab_strip->tab_at(0)->data().network_state);
}

// Verifies a tab should show its favicon.
IN_PROC_BROWSER_TEST_F(BrowserViewTest, ShowFaviconInTab) {
  // Opens "chrome://version/" page, which uses default favicon.
  const GURL version_url(chrome::kChromeUIVersionURL);
  ASSERT_TRUE(ui_test_utils::NavigateToURL(browser(), version_url));
  auto* const tab = browser()->GetTabStripModel()->GetActiveTab();
  auto* const helper = TabUIHelper::From(tab);
  ASSERT_TRUE(helper);

  const auto favicon = helper->GetFavicon();
  ASSERT_FALSE(favicon.IsEmpty());
}

// On Mac, voiceover treats tab modal dialogs as native windows, so setting an
// accessible title for tab-modal dialogs is not necessary.
#if !BUILDFLAG(IS_MAC)

// Open a tab-modal dialog and check that the accessibility tree only contains
// the dialog.
IN_PROC_BROWSER_TEST_F(BrowserViewTest, GetAccessibleTabModalDialogTree) {
  content::ScopedAccessibilityModeOverride ax_mode_override(
      ui::kAXModeComplete);
  ui::AXPlatformNode* ax_node = ui::AXPlatformNode::FromNativeViewAccessible(
      browser_view()->GetWidget()->GetRootView()->GetNativeViewAccessible());
// We expect this conversion to be safe on Windows, but can't guarantee that it
// is safe on other platforms.
#if BUILDFLAG(IS_WIN)
  ASSERT_TRUE(ax_node);
#else
  if (!ax_node) {
    return;
  }
#endif

  // There is no dialog, but the browser UI should be visible. So we expect the
  // browser's app menu button and no "OK" button from a dialog.
  EXPECT_NE(ui::AXPlatformNodeTestHelper::FindChildByName(
                ax_node, l10n_util::GetStringUTF8(IDS_ACCNAME_APP)),
            nullptr);
  EXPECT_EQ(ui::AXPlatformNodeTestHelper::FindChildByName(ax_node, "OK"),
            nullptr);

  content::WebContents* contents = browser_view()->GetActiveWebContents();
  auto delegate = std::make_unique<TestTabModalConfirmDialogDelegate>(contents);

  // `ViewAXPlatformNodeDelegate::GetChildWidgets` expects the following
  // conditions to be met in order to conclude that a tab modal dialog is
  // showing:
  // 1. The dialog is included in `Widget::GetAllOwnedWidgets()`.
  // 2. The currently-focused view is contained in the dialog.
  // Waiting for the dialog to be shown should ensure that the first
  // condition is met. But we also need to wait for the focus to change
  // or the second condition flakily fails.
  TestFocusChangeWaiter focus_waiter(browser_view()->GetFocusManager(),
                                     "MessageBoxView");
  TabModalConfirmDialog::Create(std::move(delegate), contents);
  focus_waiter.Wait();

  // The tab modal dialog should be in the accessibility tree; everything else
  // should be hidden. So we expect an "OK" button and no app menu button.
  EXPECT_EQ(ui::AXPlatformNodeTestHelper::FindChildByName(
                ax_node, l10n_util::GetStringUTF8(IDS_ACCNAME_APP)),
            nullptr);
  EXPECT_NE(ui::AXPlatformNodeTestHelper::FindChildByName(ax_node, "OK"),
            nullptr);
}
#endif  // !BUILDFLAG(IS_MAC)

// Tests that a content area scrim is shown when a tab modal dialog is active.
IN_PROC_BROWSER_TEST_F(BrowserViewTest, ScrimForTabModal) {
  content::WebContents* contents = browser_view()->GetActiveWebContents();
  auto delegate = std::make_unique<TestTabModalConfirmDialogDelegate>(contents);

  // Showing a tab modal dialog will enable the content scrim.
  TabModalConfirmDialog::Create(std::move(delegate), contents);
  EXPECT_TRUE(active_contents_scrim_view()->GetVisible());

  // Goes to a second tab will disable the content scrim.
  ASSERT_TRUE(
      AddTabAtIndex(1, GURL(url::kAboutBlankURL), ui::PAGE_TRANSITION_LINK));
  EXPECT_FALSE(active_contents_scrim_view()->GetVisible());

  // Switch back to the page that has a modal dialog.
  browser()->GetTabStripModel()->ActivateTabAt(
      0, TabStripUserGestureDetails(
             TabStripUserGestureDetails::GestureType::kMouse));
  EXPECT_TRUE(active_contents_scrim_view()->GetVisible());

  // Closing the tab disables the content scrim.
  chrome::CloseWebContents(
      browser(), browser()->GetTabStripModel()->GetActiveWebContents(),
      /*add_to_history=*/false);
}

// MacOS does not need views window scrim. We use sheet to show window modals
// (-[NSWindow beginSheet:]), which natively draws a scrim.
#if !BUILDFLAG(IS_MAC)
IN_PROC_BROWSER_TEST_F(BrowserViewTest, ScrimForBrowserWindowModal) {
  auto child_widget_delegate = std::make_unique<views::WidgetDelegate>();
  auto child_widget = std::make_unique<views::Widget>();
  child_widget_delegate->SetModalType(ui::mojom::ModalType::kWindow);
  views::Widget::InitParams params(
      views::Widget::InitParams::CLIENT_OWNS_WIDGET,
      views::Widget::InitParams::TYPE_WINDOW);
  params.delegate = child_widget_delegate.get();
  params.parent = browser_view()->GetWidget()->GetNativeView();
  child_widget->Init(std::move(params));

  child_widget->Show();
  EXPECT_TRUE(browser_view()->window_scrim_view()->GetVisible());
  child_widget->Hide();
  EXPECT_FALSE(browser_view()->window_scrim_view()->GetVisible());
  child_widget->Show();
  EXPECT_TRUE(browser_view()->window_scrim_view()->GetVisible());
  // Destroy the child widget, the parent should be notified about child modal
  // visibility change.
  child_widget.reset();
  EXPECT_FALSE(browser_view()->window_scrim_view()->GetVisible());
}
#endif  // !BUILDFLAG(IS_MAC)

// Tests that GetInactiveSplitTabIndex returns correctly with two adjacent
// splits.
IN_PROC_BROWSER_TEST_F(BrowserViewTest, SplitViewActiveIndexTest) {
  // Add enough tabs to create two split views.
  chrome::AddTabAt(browser(), GURL(), -1, true);
  chrome::AddTabAt(browser(), GURL(), -1, true);
  chrome::AddTabAt(browser(), GURL(), -1, true);
  // Add tabs to splits.
  browser()->GetTabStripModel()->ActivateTabAt(0);
  browser()->GetTabStripModel()->AddToNewSplit(
      {1}, split_tabs::SplitTabVisualData(),
      split_tabs::SplitTabCreatedSource::kToolbarButton);

  browser()->GetTabStripModel()->ActivateTabAt(2);
  browser()->GetTabStripModel()->AddToNewSplit(
      {3}, split_tabs::SplitTabVisualData(),
      split_tabs::SplitTabCreatedSource::kToolbarButton);

  browser()->GetTabStripModel()->ActivateTabAt(0);
  EXPECT_TRUE(browser_view()->multi_contents_view());
  EXPECT_EQ(
      browser_view()->multi_contents_view()->GetActiveContentsView(),
      browser_view()->multi_contents_view()->start_contents_view_for_testing());

  browser()->GetTabStripModel()->ActivateTabAt(2);
  EXPECT_EQ(
      browser_view()->multi_contents_view()->GetActiveContentsView(),
      browser_view()->multi_contents_view()->start_contents_view_for_testing());

  browser()->GetTabStripModel()->ActivateTabAt(3);
  EXPECT_EQ(
      browser_view()->multi_contents_view()->GetActiveContentsView(),
      browser_view()->multi_contents_view()->end_contents_view_for_testing());
}

// Verifies that page and devtools WebViews are being correctly laid out
// when DevTools is opened/closed/updated while docked.
//
// TODO(crbug.com/507885173): Flaky on MSAN.
#if defined(MEMORY_SANITIZER)
#define MAYBE_DevToolsDockedRemainsOpenInWithFocusInSplit \
  DISABLED_DevToolsDockedRemainsOpenInWithFocusInSplit
#else
#define MAYBE_DevToolsDockedRemainsOpenInWithFocusInSplit \
  DevToolsDockedRemainsOpenInWithFocusInSplit
#endif
IN_PROC_BROWSER_TEST_F(BrowserViewTest,
                       MAYBE_DevToolsDockedRemainsOpenInWithFocusInSplit) {
  // Add enough tabs to create two split views.
  chrome::AddTabAt(browser(), GURL(), -1, true);
  chrome::AddTabAt(browser(), GURL(), -1, true);
  chrome::AddTabAt(browser(), GURL(), -1, true);
  // Add tabs to splits.
  browser()->GetTabStripModel()->ActivateTabAt(0);
  browser()->GetTabStripModel()->AddToNewSplit(
      {1}, split_tabs::SplitTabVisualData(),
      split_tabs::SplitTabCreatedSource::kToolbarButton);
  browser()->GetTabStripModel()->ActivateTabAt(2);
  browser()->GetTabStripModel()->AddToNewSplit(
      {3}, split_tabs::SplitTabVisualData(),
      split_tabs::SplitTabCreatedSource::kToolbarButton);

  browser()->GetTabStripModel()->ActivateTabAt(0);

  // Verify neither devtools is visible.
  EXPECT_FALSE(
      active_contents_container_view()->devtools_web_view()->GetVisible());
  EXPECT_FALSE(
      inactive_contents_container_view()->devtools_web_view()->GetVisible());

  // Open devtools for the active side of the split and verify it exists only
  // for the active side.
  DevToolsWindowTesting::OpenDevToolsWindowSync(browser(), true);
  EXPECT_TRUE(
      active_contents_container_view()->devtools_web_view()->GetVisible());
  EXPECT_FALSE(
      inactive_contents_container_view()->devtools_web_view()->GetVisible());

  // Activate to the inactive side and verify it stayed open on the appropriate
  // side of the split.
  browser()->GetTabStripModel()->ActivateTabAt(1);
  EXPECT_FALSE(
      active_contents_container_view()->devtools_web_view()->GetVisible());
  EXPECT_TRUE(
      inactive_contents_container_view()->devtools_web_view()->GetVisible());

  // Activate to the other split and verify no devtools are seen.
  browser()->GetTabStripModel()->ActivateTabAt(2);
  EXPECT_FALSE(
      active_contents_container_view()->devtools_web_view()->GetVisible());
  EXPECT_FALSE(
      inactive_contents_container_view()->devtools_web_view()->GetVisible());

  // Switch back to the split where devtools is open and verify is is still
  // visible.
  browser()->GetTabStripModel()->ActivateTabAt(1);
  EXPECT_FALSE(
      active_contents_container_view()->devtools_web_view()->GetVisible());
  EXPECT_TRUE(
      inactive_contents_container_view()->devtools_web_view()->GetVisible());

  // Verify two devtools can be seen at once (one for each side of a split).
  DevToolsWindowTesting::OpenDevToolsWindowSync(browser(), true);
  EXPECT_TRUE(
      active_contents_container_view()->devtools_web_view()->GetVisible());
  EXPECT_TRUE(
      inactive_contents_container_view()->devtools_web_view()->GetVisible());
}

// Verifies that page and devtools WebViews are being correctly laid out
// when DevTools is opened/closed/updated while docked.
IN_PROC_BROWSER_TEST_F(BrowserViewTest,
                       DevToolsRemainsCorrectlyDockedAfterSwappingSplit) {
  // Add enough tabs to create two split views.
  chrome::AddTabAt(browser(), GURL(), -1, true);
  // Add tabs to splits.
  browser()->GetTabStripModel()->ActivateTabAt(0);
  browser()->GetTabStripModel()->AddToNewSplit(
      {1}, split_tabs::SplitTabVisualData(),
      split_tabs::SplitTabCreatedSource::kToolbarButton);

  browser()->GetTabStripModel()->ActivateTabAt(0);

  // Open devtools for the active side of the split and verify it exists only
  // for the active side.
  DevToolsWindowTesting::OpenDevToolsWindowSync(browser(), true);
  EXPECT_TRUE(
      active_contents_container_view()->devtools_web_view()->GetVisible());
  EXPECT_FALSE(
      inactive_contents_container_view()->devtools_web_view()->GetVisible());

  // Reverse the split and verify the correct side has devtools.
  browser_view()->multi_contents_view()->OnSwap();
  EXPECT_TRUE(
      active_contents_container_view()->devtools_web_view()->GetVisible());
  EXPECT_FALSE(
      inactive_contents_container_view()->devtools_web_view()->GetVisible());
}

IN_PROC_BROWSER_TEST_F(BrowserViewTest, DragNotSupportedInFullscreen) {
#if BUILDFLAG(IS_OZONE)
  // TODO(crbug.com/425715421): Re-enable when wayland supports drag and drop
  if (::ui::OzonePlatform::RunningOnWaylandForTest()) {
    GTEST_SKIP() << "Wayland doesn't support drag and drop in fullscreen";
  }
#endif
  // Add enough tabs to create two split views.
  chrome::AddTabAt(browser(), GURL(), -1, true);
  // Add tabs to splits.
  browser()->GetTabStripModel()->ActivateTabAt(0);
  browser()->GetTabStripModel()->AddToNewSplit(
      {1}, split_tabs::SplitTabVisualData(),
      split_tabs::SplitTabCreatedSource::kToolbarButton);

  // Make fullscreen
  ui_test_utils::ToggleFullscreenModeAndWait(browser());

  // Attempt to start a drag
  content::DropData drop_data;
  drop_data.url_infos = {
      ui::ClipboardUrlInfo(GURL("https://mail.google.com"), u"")};
  const gfx::Rect bounds = browser_view()->GetBoundsInScreen();
  const gfx::PointF point(bounds.left_center().x() + 10,
                          bounds.left_center().y());
  browser_view()->PreHandleDragUpdate(drop_data, point);

  EXPECT_FALSE(browser_view()
                   ->multi_contents_view()
                   ->drop_target_controller()
                   .IsDropTimerRunningForTesting());
}

IN_PROC_BROWSER_TEST_F(BrowserViewTest, ScrimForTabModalInSplitView) {
  // Create a split view with two tabs followed by a third that will show the
  // scrim.
  chrome::AddTabAt(browser(), GURL(), -1, true);
  chrome::AddTabAt(browser(), GURL(), -1, true);
  browser()->GetTabStripModel()->ActivateTabAt(0);
  browser()->GetTabStripModel()->AddToNewSplit(
      {1}, split_tabs::SplitTabVisualData(),
      split_tabs::SplitTabCreatedSource::kToolbarButton);

  // Show a tab modal dialog on the third tab (not part of the split).
  browser()->GetTabStripModel()->ActivateTabAt(2);
  content::WebContents* contents = browser_view()->GetActiveWebContents();
  auto delegate = std::make_unique<TestTabModalConfirmDialogDelegate>(contents);
  TabModalConfirmDialog::Create(std::move(delegate), contents);
  EXPECT_TRUE(
      active_contents_container_view()->contents_scrim_view()->GetVisible());

  // Activating a tab in the split will cause the scrim to hide.
  browser()->GetTabStripModel()->ActivateTabAt(0);
  EXPECT_FALSE(
      active_contents_container_view()->contents_scrim_view()->GetVisible());

  // Swapping the tab with the tab modal dialog into the inactive spot in the
  // split should show the scrim but not change the active tab.
  browser()->GetTabStripModel()->UpdateTabInSplit(
      browser()->GetTabStripModel()->GetTabAtIndex(1), 2,
      TabStripModel::SplitUpdateType::kSwap);
  EXPECT_EQ(0, browser()->GetTabStripModel()->active_index());
  EXPECT_TRUE(
      inactive_contents_container_view()->contents_scrim_view()->GetVisible());
  EXPECT_FALSE(
      active_contents_container_view()->contents_scrim_view()->GetVisible());
}

// TODO(crbug.com/543094230): Test is flaky.
// Tests that GetAccessibleTabLabel correctly labels each tab in a split.
IN_PROC_BROWSER_TEST_F(BrowserViewTest, DISABLED_AccessibleTabLabel) {
  auto* controller = WindowMetadataController::From(browser());

  // Create a pinned split.
  chrome::AddTabAt(browser(), GURL(), -1, true);
  browser()->GetTabStripModel()->SetTabPinned(0, true);
  browser()->GetTabStripModel()->SetTabPinned(1, true);
  browser()->GetTabStripModel()->ActivateTabAt(0);
  browser()->GetTabStripModel()->AddToNewSplit(
      {1}, split_tabs::SplitTabVisualData(),
      split_tabs::SplitTabCreatedSource::kToolbarButton);
  EXPECT_EQ(l10n_util::GetStringFUTF16(
                IDS_TAB_AX_LABEL_PINNED_FORMAT,
                l10n_util::GetStringFUTF16(
                    IDS_TAB_AX_LABEL_SPLIT_TAB_LEFT_VIEW_FORMAT,
                    controller->GetTitleForTab(browser()
                                                   ->GetTabStripModel()
                                                   ->GetTabAtIndex(0)
                                                   ->GetHandle()))),
            GetAccessibleNameForTabAt(0));
  EXPECT_EQ(l10n_util::GetStringFUTF16(
                IDS_TAB_AX_LABEL_PINNED_FORMAT,
                l10n_util::GetStringFUTF16(
                    IDS_TAB_AX_LABEL_SPLIT_TAB_RIGHT_VIEW_FORMAT,
                    controller->GetTitleForTab(browser()
                                                   ->GetTabStripModel()
                                                   ->GetTabAtIndex(1)
                                                   ->GetHandle()))),
            GetAccessibleNameForTabAt(1));

  // Create a side-by-side split.
  chrome::AddTabAt(browser(), GURL(), -1, true);
  chrome::AddTabAt(browser(), GURL(), -1, true);
  browser()->GetTabStripModel()->ActivateTabAt(2);
  browser()->GetTabStripModel()->AddToNewSplit(
      {3}, split_tabs::SplitTabVisualData(),
      split_tabs::SplitTabCreatedSource::kToolbarButton);
  EXPECT_EQ(
      l10n_util::GetStringFUTF16(
          IDS_TAB_AX_LABEL_SPLIT_TAB_LEFT_VIEW_FORMAT,
          controller->GetTitleForTab(
              browser()->GetTabStripModel()->GetTabAtIndex(2)->GetHandle())),
      GetAccessibleNameForTabAt(2));
  EXPECT_EQ(
      l10n_util::GetStringFUTF16(
          IDS_TAB_AX_LABEL_SPLIT_TAB_RIGHT_VIEW_FORMAT,
          controller->GetTitleForTab(
              browser()->GetTabStripModel()->GetTabAtIndex(3)->GetHandle())),
      GetAccessibleNameForTabAt(3));

  // Create a grouped split.
  chrome::AddTabAt(browser(), GURL(), -1, true);
  chrome::AddTabAt(browser(), GURL(), -1, true);
  browser()->GetTabStripModel()->ActivateTabAt(4);
  browser()->GetTabStripModel()->AddToNewSplit(
      {5}, split_tabs::SplitTabVisualData(),
      split_tabs::SplitTabCreatedSource::kToolbarButton);
  browser()->GetTabStripModel()->AddToNewGroup({4, 5});
  EXPECT_EQ(l10n_util::GetStringFUTF16(
                IDS_TAB_AX_LABEL_UNNAMED_GROUP_FORMAT,
                l10n_util::GetStringFUTF16(
                    IDS_TAB_AX_LABEL_SPLIT_TAB_LEFT_VIEW_FORMAT,
                    controller->GetTitleForTab(browser()
                                                   ->GetTabStripModel()
                                                   ->GetTabAtIndex(4)
                                                   ->GetHandle()))),
            GetAccessibleNameForTabAt(4));
  EXPECT_EQ(l10n_util::GetStringFUTF16(
                IDS_TAB_AX_LABEL_UNNAMED_GROUP_FORMAT,
                l10n_util::GetStringFUTF16(
                    IDS_TAB_AX_LABEL_SPLIT_TAB_RIGHT_VIEW_FORMAT,
                    controller->GetTitleForTab(browser()
                                                   ->GetTabStripModel()
                                                   ->GetTabAtIndex(5)
                                                   ->GetHandle()))),
            GetAccessibleNameForTabAt(5));

  // Create a stacked split.
  chrome::AddTabAt(browser(), GURL(), -1, true);
  chrome::AddTabAt(browser(), GURL(), -1, true);
  browser()->GetTabStripModel()->ActivateTabAt(6);
  browser()->GetTabStripModel()->AddToNewSplit(
      {7}, split_tabs::SplitTabVisualData(split_tabs::SplitTabLayout::kStacked),
      split_tabs::SplitTabCreatedSource::kToolbarButton);
  EXPECT_EQ(
      l10n_util::GetStringFUTF16(
          IDS_TAB_AX_LABEL_SPLIT_TAB_TOP_VIEW_FORMAT,
          controller->GetTitleForTab(
              browser()->GetTabStripModel()->GetTabAtIndex(6)->GetHandle())),
      GetAccessibleNameForTabAt(6));
  EXPECT_EQ(
      l10n_util::GetStringFUTF16(
          IDS_TAB_AX_LABEL_SPLIT_TAB_BOTTOM_VIEW_FORMAT,
          controller->GetTitleForTab(
              browser()->GetTabStripModel()->GetTabAtIndex(7)->GetHandle())),
      GetAccessibleNameForTabAt(7));
}

IN_PROC_BROWSER_TEST_F(BrowserViewTest,
                       ChangingSplitLayoutUpdatesAccessibleTabLabel) {
  auto* controller = WindowMetadataController::From(browser());

  // Create a side-by-side split.
  chrome::AddTabAt(browser(), GURL(), -1, true);
  split_tabs::SplitTabId split_id =
      browser()->GetTabStripModel()->AddToNewSplit(
          {0},
          split_tabs::SplitTabVisualData(
              split_tabs::SplitTabLayout::kSideBySide),
          split_tabs::SplitTabCreatedSource::kToolbarButton);

  tabs::TabInterface* start_tab =
      browser()->GetTabStripModel()->GetTabAtIndex(0);
  tabs::TabInterface* end_tab = browser()->GetTabStripModel()->GetTabAtIndex(1);

  EXPECT_EQ(l10n_util::GetStringFUTF16(
                IDS_TAB_AX_LABEL_SPLIT_TAB_LEFT_VIEW_FORMAT,
                controller->GetTitleForTab(start_tab->GetHandle())),
            GetAccessibleNameForTabAt(0));
  EXPECT_EQ(l10n_util::GetStringFUTF16(
                IDS_TAB_AX_LABEL_SPLIT_TAB_RIGHT_VIEW_FORMAT,
                controller->GetTitleForTab(end_tab->GetHandle())),
            GetAccessibleNameForTabAt(1));

  // Change the split layout to stacked.
  browser()->GetTabStripModel()->UpdateSplitLayout(
      split_id, split_tabs::SplitTabLayout::kStacked);

  EXPECT_EQ(l10n_util::GetStringFUTF16(
                IDS_TAB_AX_LABEL_SPLIT_TAB_TOP_VIEW_FORMAT,
                controller->GetTitleForTab(start_tab->GetHandle())),
            GetAccessibleNameForTabAt(0));
  EXPECT_EQ(l10n_util::GetStringFUTF16(
                IDS_TAB_AX_LABEL_SPLIT_TAB_BOTTOM_VIEW_FORMAT,
                controller->GetTitleForTab(end_tab->GetHandle())),
            GetAccessibleNameForTabAt(1));
}

#if BUILDFLAG(IS_MAC)

IN_PROC_BROWSER_TEST_F(BrowserViewTest, SplitViewFullscreenLayout) {
  // Disable always show toolbar in fullscreen
  chrome::SetAlwaysShowToolbarInFullscreenForTesting(browser(), false);

  // Create tabs and add to split
  chrome::AddTabAt(browser(), GURL(), -1, true);
  chrome::AddTabAt(browser(), GURL(), -1, true);
  browser()->GetTabStripModel()->ActivateTabAt(0);
  browser()->GetTabStripModel()->AddToNewSplit(
      {1}, split_tabs::SplitTabVisualData(),
      split_tabs::SplitTabCreatedSource::kToolbarButton);

  ASSERT_TRUE(browser()
                  ->GetTabStripModel()
                  ->selection_model()
                  .GetListSelectionModel()
                  .IsSelected(0));
  ASSERT_TRUE(browser()
                  ->GetTabStripModel()
                  ->selection_model()
                  .GetListSelectionModel()
                  .IsSelected(1));

  TopContainerView* top_container = browser_view()->top_container();
  views::View* overlay_view = browser_view()->overlay_view();

  // Verify top_container is parented to browser_view before fullscreen
  EXPECT_EQ(browser_view(), top_container->parent());
  ui_test_utils::ToggleFullscreenModeAndWait(browser());

  // Verify top_container is parented to overlay after entering fullscreen
  EXPECT_EQ(overlay_view, top_container->parent());

  browser_view()->GetExclusiveAccessContext()->ExitFullscreen();

  // Verify top_container is re-parented to browser_view after fullscreen exit
  EXPECT_EQ(browser_view(), top_container->parent());
}

IN_PROC_BROWSER_TEST_F(BrowserViewTest, SplitViewTabRevealFullscreen) {
  // Disable always show toolbar in fullscreen
  chrome::SetAlwaysShowToolbarInFullscreenForTesting(browser(), false);

  // Create tabs and add to split
  chrome::AddTabAt(browser(), GURL(), -1, true);
  chrome::AddTabAt(browser(), GURL(), -1, true);
  chrome::AddTabAt(browser(), GURL(), -1, true);
  browser()->GetTabStripModel()->ActivateTabAt(0);
  browser()->GetTabStripModel()->AddToNewSplit(
      {1}, split_tabs::SplitTabVisualData(),
      split_tabs::SplitTabCreatedSource::kToolbarButton);

  ASSERT_TRUE(browser()
                  ->GetTabStripModel()
                  ->selection_model()
                  .GetListSelectionModel()
                  .IsSelected(0));
  ASSERT_TRUE(browser()
                  ->GetTabStripModel()
                  ->selection_model()
                  .GetListSelectionModel()
                  .IsSelected(1));

  ui_test_utils::ToggleFullscreenModeAndWait(browser());
  // Entering immersive fullscreen on macOS initiates an asynchronous Cocoa
  // autohide transition. Wait for top chrome to finish hiding.
  ASSERT_TRUE(base::test::RunUntil([&]() {
    return !BrowserWindow::FromBrowser(browser())->IsToolbarShowing();
  }));

  // Switching between split tabs does not reveal top container.
  browser()->GetTabStripModel()->ActivateTabAt(1);
  ASSERT_FALSE(BrowserWindow::FromBrowser(browser())->IsToolbarShowing());

  // Switching to tab not in split should reveal top container. Wait for the
  // reveal transition to complete.
  browser()->GetTabStripModel()->ActivateTabAt(2);
  ASSERT_TRUE(base::test::RunUntil([&]() {
    return BrowserWindow::FromBrowser(browser())->IsToolbarShowing();
  }));
}
#endif

namespace {

class FakeRealTimeUrlLookupService
    : public safe_browsing::testing::FakeRealTimeUrlLookupService {
 public:
  FakeRealTimeUrlLookupService() = default;

  // RealTimeUrlLookupServiceBase:
  void StartMaybeCachedLookup(
      const GURL& url,
      safe_browsing::RTLookupResponseCallback response_callback,
      scoped_refptr<base::SequencedTaskRunner> callback_task_runner,
      SessionID session_id,
      std::optional<safe_browsing::internal::ReferringAppInfo>
          referring_app_info,
      bool use_cache) override {
    auto response = std::make_unique<safe_browsing::RTLookupResponse>();

    callback_task_runner->PostTask(
        FROM_HERE,
        base::BindOnce(std::move(response_callback),
                       /*is_rt_lookup_successful=*/true,
                       /*is_cached_response=*/true, std::move(response)));
  }
};

class BrowserViewDataProtectionTest : public InProcessBrowserTest {
 public:
  BrowserViewDataProtectionTest()
      : scoped_prewarm_feature_list_(test::ScopedPrewarmFeatureList::
                                         PrewarmState::kEnabledWithNoTrigger) {}
  BrowserViewDataProtectionTest(const BrowserViewDataProtectionTest&) = delete;
  BrowserViewDataProtectionTest& operator=(
      const BrowserViewDataProtectionTest&) = delete;

  void SetUpCommandLine(base::CommandLine* command_line) override {
    // Set a DM token since the enterprise real-time URL service expects one.
    policy::SetDMTokenForTesting(policy::DMToken::CreateValidToken("dm_token"));

    auto create_service_callback =
        base::BindRepeating([](content::BrowserContext* context) {
          Profile* profile = Profile::FromBrowserContext(context);

          // Enable real-time URL checks.
          profile->GetPrefs()->SetInteger(
              enterprise_connectors::kEnterpriseRealTimeUrlCheckMode,
              enterprise_connectors::REAL_TIME_CHECK_FOR_MAINFRAME_ENABLED);
          profile->GetPrefs()->SetInteger(
              enterprise_connectors::kEnterpriseRealTimeUrlCheckScope,
              policy::POLICY_SCOPE_MACHINE);

          auto testing_factory =
              base::BindRepeating([](content::BrowserContext* context)
                                      -> std::unique_ptr<KeyedService> {
                return std::make_unique<FakeRealTimeUrlLookupService>();
              });
          safe_browsing::ChromeEnterpriseRealTimeUrlLookupServiceFactory::
              GetInstance()
                  ->SetTestingFactory(context, testing_factory);
        });

    create_services_subscription_ =
        BrowserContextDependencyManager::GetInstance()
            ->RegisterCreateServicesCallbackForTesting(create_service_callback);
  }

  content::WebContents* NavigateAsync(const GURL& url) {
    NavigateParams params(browser(), url, ui::PAGE_TRANSITION_LINK);
    Navigate(&params);
    return params.navigated_or_inserted_contents;
  }

  void NavigateToAndWait(const GURL& url) {
    content::WaitForLoadStop(NavigateAsync(url));
  }

 private:
  base::CallbackListSubscription create_services_subscription_;
  // TODO(https://crbug.com/458274323): browser()->GetWidget() seems returning
  // a wrong Widget, one for the prewarm page, unexpectedly, might be due to
  // missing MPArch support?
  // Investigate details, and fix it to remove this workaround so that
  // DC_Screenshot test can pass stably.
  test::ScopedPrewarmFeatureList scoped_prewarm_feature_list_;
};

}  // namespace

#if BUILDFLAG(IS_WIN) || BUILDFLAG(IS_MAC)

IN_PROC_BROWSER_TEST_F(BrowserViewDataProtectionTest, DC_Screenshot) {
  data_controls::SetDataControls(browser()->GetProfile()->GetPrefs(), {R"(
        {
          "name":"block",
          "rule_id":"1234",
          "sources":{"urls":["noscreenshot.com"]},
          "restrictions":[{"class": "SCREENSHOT", "level": "BLOCK"} ]
        }
      )"});

  auto* widget = BrowserView::GetBrowserViewForBrowser(browser())->GetWidget();
  ASSERT_TRUE(widget);

  NavigateToAndWait(GURL("https://noscreenshot.com"));
  EXPECT_FALSE(widget->AreScreenshotsAllowed());

  NavigateToAndWait(GURL("https://screenshot.com"));
  EXPECT_TRUE(widget->AreScreenshotsAllowed());
}

#endif  // BUILDFLAG(IS_WIN) || BUILDFLAG(IS_MAC)

#if BUILDFLAG(IS_CHROMEOS)
IN_PROC_BROWSER_TEST_F(BrowserViewChromeOSTest, EnsureViewTreeOrder) {
  BrowserView* browser_view = BrowserView::GetBrowserViewForBrowser(browser());
  auto* const immersive_mode_controller =
      ImmersiveModeController::From(browser());

  std::vector<views::View*> children_before;
  for (const auto& child : browser_view->children()) {
    children_before.push_back(child);
  }

  EnterTabletMode();

  std::vector<views::View*> children_in_tablet;
  for (const auto& child : browser_view->children()) {
    children_in_tablet.push_back(child);
  }

  // Enter immersive fullscreen.
  ui_test_utils::ToggleFullscreenModeAndWait(browser());
  EXPECT_TRUE(immersive_mode_controller->IsEnabled());

  // Exit immersive fullscreen.
  ui_test_utils::ToggleFullscreenModeAndWait(browser());
  EXPECT_FALSE(immersive_mode_controller->IsEnabled());

  std::vector<views::View*> children_in_tablet_after_immersive;
  for (const auto& child : browser_view->children()) {
    children_in_tablet_after_immersive.push_back(child);
  }

  // View tree order before and after immersive mode should be the same in
  // tablet mode.
  EXPECT_EQ(children_in_tablet, children_in_tablet_after_immersive);

  ExitTabletMode();

  std::vector<views::View*> children_after;
  for (const auto& child : browser_view->children()) {
    children_after.push_back(child);
  }

  // View tree order should be unchanged before and after tablet mode.
  EXPECT_EQ(children_before, children_after);
}

IN_PROC_BROWSER_TEST_F(BrowserViewChromeOSTest,
                       TabStripParentedToTopContainer) {
  BrowserView* browser_view = BrowserView::GetBrowserViewForBrowser(browser());
  EXPECT_EQ(browser_view->tab_strip_view()->parent(), browser_view);

  EnterTabletMode();
  EXPECT_EQ(browser_view->tab_strip_view()->parent(),
            static_cast<views::View*>(browser_view->top_container()));

  ExitTabletMode();
  EXPECT_EQ(browser_view->tab_strip_view()->parent(), browser_view);
}
#endif  // BUILDFLAG(CHROME_OS)

namespace {

// chrome/test/data/simple.html
const char kSimplePage[] = "/simple.html";

class BrowserViewScrimPixelTest : public UiBrowserTest {
 public:
  // UiBrowserTest:
  void ShowUi(const std::string& name) override {
    ASSERT_TRUE(embedded_test_server()->Start());
    GURL url = embedded_test_server()->GetURL(kSimplePage);
    ASSERT_TRUE(ui_test_utils::NavigateToURL(browser(), url));
    browser()->GetWindow()->Show();
    BrowserView::GetBrowserViewForBrowser(browser())
        ->GetActiveContentsContainerView()
        ->contents_scrim_view()
        ->SetVisible(true);
  }

  bool VerifyUi() override {
    const auto* const test_info =
        testing::UnitTest::GetInstance()->current_test_info();
    return VerifyPixelUi(BrowserView::GetBrowserViewForBrowser(browser())
                             ->contents_container(),
                         test_info->test_suite_name(),
                         test_info->name()) != ui::test::ActionResult::kFailed;
  }

  void WaitForUserDismissal() override {
    ui_test_utils::BrowserDestroyedObserver().Wait();
  }
};

}  // namespace

IN_PROC_BROWSER_TEST_F(BrowserViewScrimPixelTest, InvokeUi_content_scrim) {
  ShowAndVerifyUi();
}

class TabAddingWidgetObserver : public views::WidgetObserver {
 public:
  explicit TabAddingWidgetObserver(BrowserWindowInterface* browser)
      : browser_(browser) {}
  ~TabAddingWidgetObserver() override = default;

  // views::WidgetObserver:
  void OnWidgetDestroying(views::Widget* widget) override {
    chrome::AddTabAt(browser_, GURL("about:blank"), -1, true);
    browser_ = nullptr;
  }

 private:
  raw_ptr<BrowserWindowInterface> browser_;
};

// Regression test for crbug.com/456102314 crash in
// ContentsWebView::CloneWebContentsLayer during synchronous widget destruction.
IN_PROC_BROWSER_TEST_F(BrowserViewTest, CloseWidgetWithTabsNoCrash) {
  BrowserWindowInterface* browser2 = CreateBrowserWindow(
      BrowserWindowCreateParams(browser()->GetProfile(),
                                /*from_user_gesture=*/true));
  chrome::AddTabAt(browser2, GURL("about:blank"), -1, true);
  EXPECT_EQ(1, browser2->GetTabStripModel()->count());

  // Directly closing the NativeWidget is a multi-step process. Tabs are removed
  // first in BrowserView::OnWidgetDestroying, and again later when the
  // associated Browser and Widget are destroyed. Add a tab after the
  // BrowserView has processed OnWidgetDestroying to exercise tabs closing in
  // the latter step (at which point the NativeWidget has been destroyed).
  TabAddingWidgetObserver observer(browser2);
  views::Widget* widget =
      BrowserView::GetBrowserViewForBrowser(browser2)->GetWidget();
  widget->AddObserver(&observer);

  // Synchronously close the associated widget and ensure the browser does not
  // crash due to operations on detached layers.
  BrowserView::GetBrowserViewForBrowser(browser2)->GetWidget()->CloseNow();
}
