File: picture_in_picture_window_manager.h

package info (click to toggle)
chromium 139.0.7258.127-1
  • links: PTS, VCS
  • area: main
  • in suites:
  • size: 6,122,068 kB
  • sloc: cpp: 35,100,771; ansic: 7,163,530; javascript: 4,103,002; python: 1,436,920; asm: 946,517; xml: 746,709; pascal: 187,653; perl: 88,691; sh: 88,436; objc: 79,953; sql: 51,488; cs: 44,583; fortran: 24,137; makefile: 22,147; tcl: 15,277; php: 13,980; yacc: 8,984; ruby: 7,485; awk: 3,720; lisp: 3,096; lex: 1,327; ada: 727; jsp: 228; sed: 36
file content (403 lines) | stat: -rw-r--r-- 17,170 bytes parent folder | download | duplicates (3)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
// 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 "base/types/pass_key.h"
#include "chrome/browser/picture_in_picture/auto_pip_setting_overlay_view.h"
#include "chrome/browser/picture_in_picture/picture_in_picture_window_manager_uma_helper.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;
class PictureInPictureWindow;
class ScopedDisallowPictureInPicture;
class ScopedTuckPictureInPicture;

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*);

  // When a website requests size `requested_size` for a document
  // picture-in-picture window (either when creating the window or when resizing
  // the window via resizeTo()/resizeBy() APIs), we restrict the maximum size
  // we'll make the pip window (this is a smaller maximum than the maximum size
  // the user can manually resize to). If the given `requested_size` is small
  // enough, this just returns the requested size. Otherwise, this shrinks the
  // requested size to fit within the constraints, and attempts to keep the
  // aspect ratio the same.
  static gfx::Size AdjustRequestedSizeIfNecessary(
      const gfx::Size& requested_size,
      const display::Display& display);

  // 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);

  // Clears the picture-in-picture window cached bounds.
  void ClearCachedBounds();

  // 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(
      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();

  // Returns true if a file dialog opened by `owner_web_contents` should create
  // a `ScopedDisallowPictureInPicture` to block picture-in-picture.
  bool ShouldFileDialogBlockPictureInPicture(
      content::WebContents* owner_web_contents);

  // Called by `ScopedDisallowPictureInPicture` to block picture-in-picture
  // windows from opening, as well as close any existing picture-in-picture
  // windows.
  void OnScopedDisallowPictureInPictureCreated(
      base::PassKey<ScopedDisallowPictureInPicture>);
  void OnScopedDisallowPictureInPictureDestroyed(
      base::PassKey<ScopedDisallowPictureInPicture>);

  // Called by a picture-in-picture window (either video picture-in-picture or
  // document picture-in-picture) when it is opened. This allows us to
  // communicate directly with the window for things that can't be handled
  // through the PictureInPictureWindowController.
  void OnPictureInPictureWindowShown(PictureInPictureWindow* window);

  // Called by a picture-in-picture window when it closes or hides to end the
  // connection opened by a previous call to `OnPictureInPictureWindowShown`.
  void OnPictureInPictureWindowHidden(PictureInPictureWindow* window);

  // Returns true if a file dialog opened by `owner_web_contents` should create
  // a `ScopedTuckPictureInPicture` to tuck picture-in-picture.
  bool ShouldFileDialogTuckPictureInPicture(
      content::WebContents* owner_web_contents);

  // Called by `ScopedTuckPictureInPicture` to force-tuck any existing or future
  // picture-in-picture windows until it's destroyed.
  void OnScopedTuckPictureInPictureCreated(
      base::PassKey<ScopedTuckPictureInPicture>);
  void OnScopedTuckPictureInPictureDestroyed(
      base::PassKey<ScopedTuckPictureInPicture>);

  // Returns true if picture-in-picture windows are currently force-tucked (e.g.
  // due to a ScopedTuckPictureInPicture object existing).
  bool IsPictureInPictureForceTucked() const;
#endif

  // Returns true if picture-in-picture is currently disabled (e.g. due to a
  // ScopedDisallowPictureInPicture object existing).
  bool IsPictureInPictureDisabled() const;

  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);

#if !BUILDFLAG(IS_ANDROID)
  void set_uma_helper_for_testing(
      std::unique_ptr<PictureInPictureWindowManagerUmaHelper> uma_helper) {
    uma_helper_ = std::move(uma_helper);
  }
#endif  // !BUILDFLAG(IS_ANDROID)

 private:
  friend struct base::DefaultSingletonTraits<PictureInPictureWindowManager>;
  class VideoWebContentsObserver;
#if !BUILDFLAG(IS_ANDROID)
  class DocumentWebContentsObserver;

  // These values are persisted to logs. Entries should not be renumbered and
  // numeric values should never be reused.
  //
  // LINT.IfChange(PictureInPictureDisallowedType)
  enum class PictureInPictureDisallowedType {
    // An existing picture-in-picture window was closed because we started
    // disallowing picture-in-picture windows.
    kExistingWindowClosed = 0,

    // A new picture-in-picture window was closed because it was created while
    // we were already disallowing picture-in-picture windows.
    kNewWindowClosed = 1,

    kMaxValue = kNewWindowClosed,
  };
  // LINT.ThenChange(//tools/metrics/histograms/metadata/media/enums.xml:PictureInPictureDisallowedTypeEnum)

  // These values are persisted to logs. Entries should not be renumbered and
  // numeric values should never be reused.
  //
  // LINT.IfChange(PictureInPictureTuckedType)
  enum class PictureInPictureTuckedType {
    // An existing picture-in-picture window was tucked because we started
    // force-tucking picture-in-picture windows.
    kExistingWindowTucked = 0,

    // A new picture-in-picture window was tucked because it was created while
    // we were already force-tucking picture-in-picture windows.
    kNewWindowTucked = 1,

    kMaxValue = kNewWindowTucked,
  };
  // LINT.ThenChange(//tools/metrics/histograms/metadata/media/enums.xml:PictureInPictureTuckedTypeEnum)
#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);

  // Records whether a new or existing picture-in-picture window was closed due
  // to an existing ScopedDisallowPictureInPicture.
  void RecordPictureInPictureDisallowed(PictureInPictureDisallowedType type);

  // Records whether a new or existing picture-in-picture window was tucked due
  // to an existing ScopedTuckPictureInPicture.
  void RecordPictureInPictureTucked(PictureInPictureTuckedType type);
#endif  // !BUILDFLAG(IS_ANDROID)

#if !BUILDFLAG(IS_ANDROID)
  // Records the total time spent on a picture in picture window, regardless of
  // the Picture-in-Picture window type (document vs video) and the reason for
  // closing the window (UI interaction, returning back to opener tab, etc.).
  //
  // The metric is recorded using the `PictureInPictureWindowManagerUmaHelper`,
  // which this method will create if one does not already exist.
  void MaybeRecordPictureInPictureChanged(bool is_picture_in_picture);
#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_;

  // The number of `ScopedDisallowPictureInPicture` objects currently in
  // existence. If at least one exists, then picture-in-picture windows will be
  // blocked.
  uint32_t number_of_existing_scoped_disallow_picture_in_pictures_ = 0;

  // True if we're currently calculating document pip's initial size. Used to
  // determine whether we should record the
  // `Media.DocumentPictureInPicture.RequestedLargeInitialSize` metric and
  // should be removed when that metric is removed.
  bool is_calculating_initial_document_pip_size_ = false;

  // The number of `ScopedTuckPictureInPicture` objects currently in
  // existence. If at least one exists, then picture-in-picture windows will be
  // tucked.
  uint32_t number_of_existing_scoped_tuck_picture_in_pictures_ = 0;

  // Pointer to the currently shown picture-in-picture window, if any.
  raw_ptr<PictureInPictureWindow> picture_in_picture_window_ = nullptr;

  std::unique_ptr<PictureInPictureWindowManagerUmaHelper> uma_helper_;
#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_