blob: 425bb40b6f9ccfb462c04ae5f57090c0477b3ebb [file] [log] [blame]
// Copyright 2018 The Chromium Authors
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#ifndef CHROME_BROWSER_PICTURE_IN_PICTURE_PICTURE_IN_PICTURE_WINDOW_MANAGER_H_
#define CHROME_BROWSER_PICTURE_IN_PICTURE_PICTURE_IN_PICTURE_WINDOW_MANAGER_H_
#include <functional>
#include <optional>
#include "base/memory/raw_ptr.h"
#include "base/memory/singleton.h"
#include "base/observer_list.h"
#include "base/observer_list_types.h"
#include "build/build_config.h"
#include "third_party/blink/public/mojom/picture_in_picture_window_options/picture_in_picture_window_options.mojom.h"
#include "ui/gfx/geometry/rect.h"
#include "ui/views/bubble/bubble_border.h"
#include "url/gurl.h"
#if !BUILDFLAG(IS_ANDROID)
#include "chrome/browser/picture_in_picture/auto_pip_setting_overlay_view.h"
#endif // !BUILDFLAG(IS_ANDROID)
namespace content {
enum class PictureInPictureResult;
class PictureInPictureWindowController;
class WebContents;
} // namespace content
namespace display {
class Display;
} // namespace display
#if !BUILDFLAG(IS_ANDROID)
class PictureInPictureOcclusionTracker;
namespace views {
class View;
} // namespace views
#endif
struct NavigateParams;
// PictureInPictureWindowManager is a singleton that handles the lifetime of the
// current Picture-in-Picture window and its PictureInPictureWindowController.
// The class also guarantees that only one window will be present per Chrome
// instances regardless of the number of windows, tabs, profiles, etc.
class PictureInPictureWindowManager {
public:
// Observer for PictureInPictureWindowManager events.
class Observer : public base::CheckedObserver {
public:
virtual void OnEnterPictureInPicture() {}
};
// Returns the singleton instance.
static PictureInPictureWindowManager* GetInstance();
PictureInPictureWindowManager(const PictureInPictureWindowManager&) = delete;
PictureInPictureWindowManager& operator=(
const PictureInPictureWindowManager&) = delete;
// Shows a PIP window using the window controller for a video element.
//
// This mode is triggered through WebContentsDelegate::EnterPictureInPicture,
// and the default implementation of that fails with a kNotSupported
// result. For compatibility, this method must also return a
// content::PictureInPictureResult even though it doesn't fail.
content::PictureInPictureResult EnterVideoPictureInPicture(
content::WebContents*);
#if !BUILDFLAG(IS_ANDROID)
// Shows a PIP window using the window controller for document picture in
// picture.
//
// Document picture-in-picture mode is triggered from the Renderer via
// WindowOpenDisposition::NEW_PICTURE_IN_PICTURE, and the browser
// (i.e. Chrome's BrowserNavigator) then calls this method to create the
// window. There's no corresponding path through the WebContentsDelegate, so
// it doesn't have a failure state.
void EnterDocumentPictureInPicture(content::WebContents* parent_web_contents,
content::WebContents* child_web_contents);
#endif // !BUILDFLAG(IS_ANDROID)
// Shows a PIP window with an explicitly provided window controller. This is
// used by ChromeOS ARC windows which do not have a WebContents as the source.
void EnterPictureInPictureWithController(
content::PictureInPictureWindowController* pip_window_controller);
// Expected behavior of the window UI-initiated close.
enum class UiBehavior {
// Close the window, but don't try to pause the video. This is also the
// behavior of `ExitPictureInPicture()`.
kCloseWindowOnly,
// Close the window, and also pause the video.
kCloseWindowAndPauseVideo,
// Act like the back-to-tab button: focus the opener window, and don't pause
// the video.
kCloseWindowAndFocusOpener,
};
// The user has requested to close the pip window. This is similar to
// `ExitPictureInPicture()`, except that it's strictly user-initiated via the
// window UI.
bool ExitPictureInPictureViaWindowUi(UiBehavior behavior);
// Closes any existing picture-in-picture windows (video or document pip).
// Returns true if a picture-in-picture window was closed, and false if there
// were no picture-in-picture windows to close.
bool ExitPictureInPicture();
// Called to notify that the initiator web contents should be focused.
void FocusInitiator();
// Gets the web contents in the opener browser window.
content::WebContents* GetWebContents() const;
// Gets the web contents in the PiP window. This only applies to document PiP
// and will be null for video PiP.
content::WebContents* GetChildWebContents() const;
// Helper method that will check if the given WebContents is hosted in a
// document PiP window.
static bool IsChildWebContents(content::WebContents*);
// Returns the window bounds of the video picture-in-picture or the document
// picture-in-picture if either of them is present.
std::optional<gfx::Rect> GetPictureInPictureWindowBounds() const;
// Used for Document picture-in-picture windows only. The returned dimensions
// represent the outer window bounds.
// This method is called from the |BrowserNavigator|. Note that the window
// bounds may be later re-adjusted by the |PictureInPictureBrowserFrameView|
// to accommodate non-client view elements, while respecting the minimum inner
// window size.
gfx::Rect CalculateInitialPictureInPictureWindowBounds(
const blink::mojom::PictureInPictureWindowOptions& pip_options,
const display::Display& display);
// Used for Document picture-in-picture windows only. The returned dimensions
// represent the outer window bounds.
// This method is called from |PictureInPictureBrowserFrameView|. Picture in
// picture window bounds are only adjusted when, the requested window size
// would cause the minimum inner window size to be smaller than the allowed
// minimum (|GetMinimumInnerWindowSize|).
gfx::Rect CalculateOuterWindowBounds(
const blink::mojom::PictureInPictureWindowOptions& pip_options,
const display::Display& display,
const gfx::Size& minimum_window_size,
const gfx::Size& excluded_margin);
// Update the most recent window bounds for the pip window in the cache. Call
// this when the pip window moves or resizes, though it's okay if not every
// update makes it here.
void UpdateCachedBounds(const gfx::Rect& most_recent_bounds);
// Used for Document picture-in-picture windows only.
// Note that this is meant to represent the inner window bounds. When the pip
// window is drawn, outer bounds may be greater than kMinWindowSize to
// accommodate window decorations and ensure the inner bound minimum size
// respects kMinWindowSize.
static gfx::Size GetMinimumInnerWindowSize();
// Used for Document picture-in-picture windows only.
static gfx::Size GetMaximumWindowSize(const display::Display& display);
// Properly sets the `window_action` on `params`.
static void SetWindowParams(NavigateParams& params);
void AddObserver(Observer* observer) { observers_.AddObserver(observer); }
void RemoveObserver(Observer* observer) {
observers_.RemoveObserver(observer);
}
// Notify observers that picture-in-picture window is created.
void NotifyObserversOnEnterPictureInPicture();
#if !BUILDFLAG(IS_ANDROID)
std::unique_ptr<AutoPipSettingOverlayView> GetOverlayView(
const gfx::Rect& browser_view_overridden_bounds,
views::View* anchor_view,
views::BubbleBorder::Arrow arrow);
// Returns the PictureInPictureOcclusionTracker, which can inform observers
// when a widget has been occluded by a video or document picture-in-picture
// window.
PictureInPictureOcclusionTracker* GetOcclusionTracker();
#endif
void set_window_controller_for_testing(
content::PictureInPictureWindowController* controller) {
pip_window_controller_ = controller;
}
// Return true if and only if the URL is can be used as an opener. This check
// allows us to explicitly opt-in opener URL types, to ensure that the pip
// window's title bar is formatted properly. Allowing any secure context, for
// example, might result in a misleading window title.
static bool IsSupportedForDocumentPictureInPicture(const GURL& url);
private:
friend struct base::DefaultSingletonTraits<PictureInPictureWindowManager>;
class VideoWebContentsObserver;
#if !BUILDFLAG(IS_ANDROID)
class DocumentWebContentsObserver;
#endif // !BUILDFLAG(IS_ANDROID)
// Create a Picture-in-Picture window and register it in order to be closed
// when needed.
// This is suffixed with "Internal" because `CreateWindow` is part of the
// Windows API.
void CreateWindowInternal(content::WebContents*);
// Closes the active Picture-in-Picture window.
// There MUST be a window open.
// This is suffixed with "Internal" to keep consistency with the method above.
void CloseWindowInternal();
#if !BUILDFLAG(IS_ANDROID)
// Called when the document PiP parent web contents is being destroyed.
void DocumentWebContentsDestroyed();
#endif // !BUILDFLAG(IS_ANDROID)
// Exits picture in picture soon, but not before this call returns. If
// picture in picture closes between now and then, that's okay. Intended as a
// helper class for callbacks, to avoid re-entrant calls during pip set-up.
static void ExitPictureInPictureSoon();
#if !BUILDFLAG(IS_ANDROID)
// Creates the `occlusion_tracker_` if it does not already exist and should
// exist.
void CreateOcclusionTrackerIfNecessary();
// Records metrics about the requested size of a document picture-in-picture
// window.
void RecordDocumentPictureInPictureRequestedSizeMetrics(
const blink::mojom::PictureInPictureWindowOptions& pip_options,
const display::Display& display);
#endif // !BUILDFLAG(IS_ANDROID)
PictureInPictureWindowManager();
~PictureInPictureWindowManager();
// Observers that listen to updates of this instance.
base::ObserverList<Observer> observers_;
std::unique_ptr<VideoWebContentsObserver> video_web_contents_observer_;
#if !BUILDFLAG(IS_ANDROID)
std::unique_ptr<DocumentWebContentsObserver> document_web_contents_observer_;
std::unique_ptr<PictureInPictureOcclusionTracker> occlusion_tracker_;
#endif //! BUILDFLAG(IS_ANDROID)
raw_ptr<content::PictureInPictureWindowController, DanglingUntriaged>
pip_window_controller_ = nullptr;
};
#endif // CHROME_BROWSER_PICTURE_IN_PICTURE_PICTURE_IN_PICTURE_WINDOW_MANAGER_H_