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

content / browser / media / captured_surface_controller.h [blame]

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

#ifndef CONTENT_BROWSER_MEDIA_CAPTURED_SURFACE_CONTROLLER_H_
#define CONTENT_BROWSER_MEDIA_CAPTURED_SURFACE_CONTROLLER_H_

#include <memory>

#include "base/callback_list.h"
#include "base/functional/callback.h"
#include "base/memory/weak_ptr.h"
#include "content/browser/media/captured_surface_control_permission_manager.h"
#include "content/common/content_export.h"
#include "content/public/browser/browser_thread.h"
#include "content/public/browser/global_routing_id.h"
#include "content/public/browser/web_contents.h"
#include "content/public/browser/web_contents_media_capture_id.h"
#include "third_party/blink/public/mojom/mediastream/media_stream.mojom.h"

namespace content {

// Encapsulates the permission state and logic associated with the Captured
// Surface Control API. Objects of this class live on the IO thread.
class CONTENT_EXPORT CapturedSurfaceController {
 public:
  using CapturedSurfaceControlResult =
      ::blink::mojom::CapturedSurfaceControlResult;

  // Wheel deltas are clamped to this arbitrary,  reasonable value.
  // We clamp rather than report an error because "reasonable" is not
  // well-defined as of the time being, let alone in an interoperable way.
  inline static constexpr int32_t kMaxWheelDeltaMagnitude = 1000000;

  static std::unique_ptr<CapturedSurfaceController> CreateForTesting(
      GlobalRenderFrameHostId capturer_rfh_id,
      WebContentsMediaCaptureId captured_wc_id,
      std::unique_ptr<CapturedSurfaceControlPermissionManager>
          permission_manager,
      base::RepeatingCallback<void(int)> on_zoom_level_change_callback,
      base::RepeatingCallback<void(base::WeakPtr<WebContents>)>
          wc_resolution_callback);

  CapturedSurfaceController(
      GlobalRenderFrameHostId capturer_rfh_id,
      WebContentsMediaCaptureId captured_wc_id,
      base::RepeatingCallback<void(int)> on_zoom_level_change_callback);

  virtual ~CapturedSurfaceController();

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

  // Set the captured WebContents this controller is associated with.
  // This may be called with a null `WebContentsMediaCaptureId`.
  virtual void UpdateCaptureTarget(WebContentsMediaCaptureId captured_wc_id);

  // Produce a wheel event on the captured surface.
  virtual void SendWheel(
      blink::mojom::CapturedWheelActionPtr action,
      base::OnceCallback<void(CapturedSurfaceControlResult)> reply_callback);

  // Set the zoom level of the captured tab.
  virtual void SetZoomLevel(
      int zoom_level,
      base::OnceCallback<void(CapturedSurfaceControlResult)> reply_callback);

  virtual void RequestPermission(
      base::OnceCallback<void(CapturedSurfaceControlResult)> reply_callback);

  struct CapturedSurfaceInfo final {
    CapturedSurfaceInfo(
        base::WeakPtr<WebContents> captured_wc,
        std::unique_ptr<base::CallbackListSubscription,
                        BrowserThread::DeleteOnUIThread> subscription,
        int subscription_version,
        int initial_zoom_level);
    CapturedSurfaceInfo(CapturedSurfaceInfo&& other);
    CapturedSurfaceInfo& operator=(CapturedSurfaceInfo&& other);
    ~CapturedSurfaceInfo();

    base::WeakPtr<WebContents> captured_wc;
    std::unique_ptr<base::CallbackListSubscription,
                    BrowserThread::DeleteOnUIThread>
        subscription;
    int subscription_version;
    int initial_zoom_level;
  };

 private:
  using PermissionResult =
      CapturedSurfaceControlPermissionManager::PermissionResult;

  void OnZoomLevelChange(int subscription_version, int zoom_level);

  CapturedSurfaceController(
      GlobalRenderFrameHostId capturer_rfh_id,
      WebContentsMediaCaptureId captured_wc_id,
      std::unique_ptr<CapturedSurfaceControlPermissionManager>
          permission_manager,
      base::RepeatingCallback<void(int)> on_zoom_level_change_callback,
      base::RepeatingCallback<void(base::WeakPtr<WebContents>)>
          wc_resolution_callback);

  // Manage the resolution of WebContents-IDs into base::WeakPtr<WebContents>.
  void ResolveCapturedSurface(WebContentsMediaCaptureId captured_wc_id);
  void OnCapturedSurfaceResolved(
      std::optional<CapturedSurfaceInfo> captured_surface);

  const GlobalRenderFrameHostId capturer_rfh_id_;

  // References the captured tab through its WebContents.
  //
  // Set to nullopt when:
  // * The captured surface is not a tab.
  // * Right after construction, before the ID is first resolved (on the
  //   UI thread) to a valid base::WeakPtr<WebContents>.
  // * Whenever the captured tab changes, and UpdateCaptureTarget() is
  //   called. This triggers a new resolution, and in the intervening time,
  //   this will be set back to nullptr.
  //
  // Set to a concrete value otherwise.
  // However, this concrete value can be nullptr, (1) as with any WeakPtr,
  // or (2) if the ID failed to resolve to a valid WebContents.
  //
  // Note that `this` lives on the IO thread, and it is not possible to
  // check the value of the underlying WebContents* here, or even compare
  // it to nullptr.
  //
  // In the unlikely-yet-possible case that SendWheel() or SetZoomLevel()
  // are called while the task to resolve is pending, those calls will
  // fail gracefully. Subsequent calls are valid and can succeed.
  // TODO(crbug.com/41493349): Add UMA to measure how often this happens
  // and determine whether it's worth the effort to fix.
  std::optional<base::WeakPtr<WebContents>> captured_wc_;

  // Counts the pending resolutions, so that `captured_wc_` would only
  // be set to a concrete values when the last one resolves.
  int pending_wc_resolutions_ = 0;

  std::unique_ptr<CapturedSurfaceControlPermissionManager> permission_manager_;

  // Callback to be invoked whenever an ID's resolution to a
  // base::WeakPtr<WebContents> completes.
  const base::RepeatingCallback<void(base::WeakPtr<WebContents>)>
      wc_resolution_callback_;

  // `zoom_level_subscription_` controls the lifetime of a subscriptions to
  // zoom-level updates for a captured tab. If the capture is switched over to a
  // new tab, the subscription is re-initialized and `subscription_version_` is
  // incremented so that callback invocations from the previous subscriptions
  // may be ignored.
  std::unique_ptr<base::CallbackListSubscription,
                  BrowserThread::DeleteOnUIThread>
      zoom_level_subscription_;
  int subscription_version_ = 0;

  std::optional<int> current_zoom_level_;
  const base::RepeatingCallback<void(int)> on_zoom_level_change_callback_;

  base::WeakPtrFactory<CapturedSurfaceController> weak_factory_{this};
};

}  // namespace content

#endif  // CONTENT_BROWSER_MEDIA_CAPTURED_SURFACE_CONTROLLER_H_