chromium/chrome/browser/picture_in_picture/document_picture_in_picture_window_controller_browsertest.cc

// Copyright 2022 The Chromium Authors
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.

#include "content/public/browser/document_picture_in_picture_window_controller.h"

#include "base/barrier_closure.h"
#include "base/files/file_util.h"
#include "base/functional/bind.h"
#include "base/functional/callback.h"
#include "base/memory/raw_ptr.h"
#include "base/path_service.h"
#include "base/scoped_observation.h"
#include "base/strings/strcat.h"
#include "base/test/scoped_feature_list.h"
#include "base/test/test_timeouts.h"
#include "build/build_config.h"
#include "build/chromeos_buildflags.h"
#include "chrome/browser/chrome_content_browser_client.h"
#include "chrome/browser/devtools/devtools_window_testing.h"
#include "chrome/browser/picture_in_picture/picture_in_picture_window_manager.h"
#include "chrome/browser/platform_util.h"
#include "chrome/browser/ui/browser.h"
#include "chrome/browser/ui/browser_commands.h"
#include "chrome/browser/ui/browser_finder.h"
#include "chrome/browser/ui/browser_window.h"
#include "chrome/browser/ui/tabs/tab_strip_model.h"
#include "chrome/browser/ui/views/frame/browser_view.h"
#include "chrome/browser/ui/views/frame/picture_in_picture_browser_frame_view.h"
#include "chrome/browser/ui/web_applications/app_browser_controller.h"
#include "chrome/browser/ui/web_applications/test/web_app_browsertest_util.h"
#include "chrome/browser/ui/web_applications/web_app_browsertest_base.h"
#include "chrome/browser/web_applications/web_app_install_info.h"
#include "chrome/test/base/in_process_browser_test.h"
#include "chrome/test/base/ui_test_utils.h"
#include "components/viz/common/frame_sinks/copy_output_request.h"
#include "components/viz/common/frame_sinks/copy_output_result.h"
#include "content/public/browser/media_session.h"
#include "content/public/browser/overlay_window.h"
#include "content/public/browser/render_frame_host.h"
#include "content/public/browser/render_view_host.h"
#include "content/public/browser/render_widget_host_view.h"
#include "content/public/browser/web_contents.h"
#include "content/public/common/content_client.h"
#include "content/public/common/content_features.h"
#include "content/public/common/content_switches.h"
#include "content/public/test/browser_test.h"
#include "content/public/test/browser_test_utils.h"
#include "content/public/test/fenced_frame_test_util.h"
#include "content/public/test/media_start_stop_observer.h"
#include "content/public/test/prerender_test_util.h"
#include "content/public/test/test_navigation_observer.h"
#include "media/base/media_switches.h"
#include "net/dns/mock_host_resolver.h"
#include "net/test/embedded_test_server/embedded_test_server.h"
#include "services/media_session/public/cpp/features.h"
#include "services/network/public/cpp/is_potentially_trustworthy.h"
#include "skia/ext/image_operations.h"
#include "testing/gmock/include/gmock/gmock.h"
#include "third_party/blink/public/common/features.h"
#include "third_party/blink/public/common/web_preferences/web_preferences.h"
#include "ui/compositor/compositor.h"
#include "ui/compositor/layer.h"
#include "ui/compositor/test/draw_waiter_for_test.h"
#include "ui/display/display.h"
#include "ui/display/display_switches.h"
#include "ui/display/screen.h"
#include "ui/events/base_event_utils.h"
#include "ui/gfx/codec/png_codec.h"
#include "ui/views/controls/button/image_button.h"
#include "ui/views/test/button_test_api.h"
#include "ui/views/view_observer.h"
#include "ui/views/widget/widget_observer.h"

#if BUILDFLAG(IS_CHROMEOS_ASH)
#include "chromeos/ui/base/chromeos_ui_constants.h"
#include "ui/aura/window.h"
#include "ui/base/hit_test.h"
#include "ui/events/test/event_generator.h"
#endif

EvalJs;
ExecJs;
_;

namespace {

const base::FilePath::CharType kPictureInPictureDocumentPipPage[] =);

// Observes a views::Widget and waits for it to be active or inactive.
class WidgetActivationWaiter : public views::WidgetObserver {};

class DocumentPictureInPictureWindowControllerBrowserTest
    : public InProcessBrowserTest,
      public testing::WithParamInterface<gfx::Size> {};

// Helper class that waits without polling a run loop until a condition is met.
// Note that it does not ever check the condition itself; some other thing, like
// an observer (see below), must notice that the condition is set as part of
// running the RunLoop.  One must derive a subclass to do whatever specific type
// of checks are required.
class TestConditionWaiter {};

// Specialization of `TestConditionWaiter` that's useful for many types of
// observers.  The template argument is the observer type (e.g.,
// views::WidgetObserver).  Derive from this class, and implement whatever
// methods on `ObserverType` you need.  The subclass should call
// `CheckCondition()` to see if the condition is met.
template <typename ObserverType>
class TestObserverWaiter : public TestConditionWaiter, public ObserverType {};

}  // namespace

// Checks the creation of the window controller, as well as basic window
// creation, visibility and activation.
IN_PROC_BROWSER_TEST_F(DocumentPictureInPictureWindowControllerBrowserTest,
                       CreationAndVisibilityAndActivation) {}

// Regression test for https://crbug.com/1296780 - opening a picture-in-picture
// window twice in a row should work, closing the old window before opening the
// new one.
IN_PROC_BROWSER_TEST_F(DocumentPictureInPictureWindowControllerBrowserTest,
                       CreateTwice) {}

// Tests closing the document picture-in-picture window.
IN_PROC_BROWSER_TEST_F(DocumentPictureInPictureWindowControllerBrowserTest,
                       CloseWindow) {}

// Tests navigating the opener closes the picture in picture window.
IN_PROC_BROWSER_TEST_F(DocumentPictureInPictureWindowControllerBrowserTest,
                       ClosePictureInPictureWhenOpenerNavigates) {}

// Navigation by the pip window to a new document should close the pip window.
IN_PROC_BROWSER_TEST_F(DocumentPictureInPictureWindowControllerBrowserTest,
                       CloseOnPictureInPictureNavigationToNewDocument) {}

// Navigation within the pip window's document should not close the pip window.
IN_PROC_BROWSER_TEST_F(DocumentPictureInPictureWindowControllerBrowserTest,
                       DoNotCloseOnPictureInPictureNavigationInsideDocument) {}

// Refreshing the pip window's document should close the pip window.
IN_PROC_BROWSER_TEST_F(DocumentPictureInPictureWindowControllerBrowserTest,
                       CloseOnPictureInPictureRefresh) {}

// Explicitly navigating to about:blank should close the pip window.
// Regression test for https://crbug.com/1413919.
IN_PROC_BROWSER_TEST_F(DocumentPictureInPictureWindowControllerBrowserTest,
                       CloseOnPictureInPictureNavigatedToAboutBlank) {}

// Explicitly navigating to the empty string should close the pip window.
// Regression test for https://crbug.com/1413919.
IN_PROC_BROWSER_TEST_F(DocumentPictureInPictureWindowControllerBrowserTest,
                       CloseOnPictureInPictureNavigatedToEmptyString) {}

// Adding a script to the popup window should not crash.
IN_PROC_BROWSER_TEST_F(DocumentPictureInPictureWindowControllerBrowserTest,
                       AddScriptToPictureInPictureWindow) {}

// Window controller bounds should be same as the web content bounds.
IN_PROC_BROWSER_TEST_F(DocumentPictureInPictureWindowControllerBrowserTest,
                       CheckWindowBoundsSameAsWebContents) {}

#if BUILDFLAG(IS_WIN)
// Back to tab button (PictureInPictureBrowserFrameView) is not available
// in Windows yet.
#define MAYBE_FocusInitiatorWhenBackToTab
#else
#define MAYBE_FocusInitiatorWhenBackToTab
#endif
IN_PROC_BROWSER_TEST_F(DocumentPictureInPictureWindowControllerBrowserTest,
                       MAYBE_FocusInitiatorWhenBackToTab) {}

// Make sure that document PiP fails without a secure context.
// TODO(crbug.com/40842257): Consider replacing this with a web platform test.
IN_PROC_BROWSER_TEST_F(DocumentPictureInPictureWindowControllerBrowserTest,
                       RequiresSecureContext) {}

// Make sure that inner bounds of document PiP windows are not smaller than the
// allowed minimum size.
IN_PROC_BROWSER_TEST_F(DocumentPictureInPictureWindowControllerBrowserTest,
                       MinimumWindowInnerBounds) {}

// Make sure that outer bounds of document PiP windows do not exceed the allowed
// maximum size.
IN_PROC_BROWSER_TEST_F(DocumentPictureInPictureWindowControllerBrowserTest,
                       MaximumWindowOuterBounds) {}

// Context menu should not be shown when right clicking on a document picture in
// picture window title.
IN_PROC_BROWSER_TEST_F(DocumentPictureInPictureWindowControllerBrowserTest,
                       ContextMenuIsDisabled) {}

IN_PROC_BROWSER_TEST_F(DocumentPictureInPictureWindowControllerBrowserTest,
                       WindowClosesEvenIfDisconnectedFromWindowManager) {}

#if BUILDFLAG(IS_CHROMEOS_ASH)
// Verify that it is possible to resize a document picture in picture window
// using the resize outside bound in ChromeOS ASH.
IN_PROC_BROWSER_TEST_F(DocumentPictureInPictureWindowControllerBrowserTest,
                       CanResizeUsingOutsideBounds) {
  // Attempt to create a Document PiP window with the minimum window size.
  LoadTabAndEnterPictureInPicture(
      browser(), PictureInPictureWindowManager::GetMinimumInnerWindowSize());
  auto* pip_web_contents = window_controller()->GetChildWebContents();
  ASSERT_NE(nullptr, pip_web_contents);
  WaitForPageLoad(pip_web_contents);

  // Get a point within the resize outside bounds.
  auto* browser_view = static_cast<BrowserView*>(
      BrowserWindow::FindBrowserWindowWithWebContents(pip_web_contents));
  const auto left_center_point = browser_view->GetBounds().left_center();
  const auto resize_outside_bound_point =
      gfx::Point(left_center_point.x() - chromeos::kResizeInsideBoundsSize -
                     chromeos::kResizeOutsideBoundsSize / 2,
                 left_center_point.y());

  // Perform a click on the left resize outside bound, followed by a drag to the
  // left.
  aura::Window* window = browser_view->GetNativeWindow();
  const auto initial_window_size = window->GetBoundsInScreen().size();
  ui::test::EventGenerator event_generator(window->GetRootWindow());
  event_generator.set_current_screen_location(resize_outside_bound_point);
  const int drag_distance = 10;
  event_generator.DragMouseBy(-drag_distance, 0);

  // Verify that the rezise took place.
  const auto expected_size = initial_window_size + gfx::Size(drag_distance, 0);
  ASSERT_EQ(expected_size, window->GetBoundsInScreen().size());
}
#endif  // BUILDFLAG(IS_CHROMEOS_ASH)

IN_PROC_BROWSER_TEST_F(DocumentPictureInPictureWindowControllerBrowserTest,
                       WindowBoundsAreCached) {}

INSTANTIATE_TEST_SUITE_P();

#if BUILDFLAG(IS_LINUX)
// TODO(crbug.com/40923223): Fix and re-enable this test for Linux.
// This test is flaky on Linux, sometimes the window origin is not updated
// before the test harness timeout.
#define MAYBE_VerifyWindowMargins
#else
#define MAYBE_VerifyWindowMargins
#endif
// Test that the document PiP window margins are correct.
IN_PROC_BROWSER_TEST_P(DocumentPictureInPictureWindowControllerBrowserTest,
                       MAYBE_VerifyWindowMargins) {}

IN_PROC_BROWSER_TEST_F(DocumentPictureInPictureWindowControllerBrowserTest,
                       DoNotDeferMediaLoadIfWindowOpened) {}

IN_PROC_BROWSER_TEST_F(DocumentPictureInPictureWindowControllerBrowserTest,
                       MatchMediaQuery) {}

// Make sure that inner bounds of document PiP windows match the requested size.
IN_PROC_BROWSER_TEST_F(DocumentPictureInPictureWindowControllerBrowserTest,
                       InnerBoundsMatchRequest) {}

// When `window.open()` is called from a picture-in-picture window, it must lose
// focus to the newly opened window to prevent multiple popunders from opening
// when a user types multiple keys in a picture-in-picture window.
IN_PROC_BROWSER_TEST_F(DocumentPictureInPictureWindowControllerBrowserTest,
                       WindowOpenLosesFocus) {}