[Capture] Cleanup FrameSinkVideoCapturer.ChangeTarget

This patch cleans up the ChangeTarget API, adding appropriate
StructTraits and UnionTraits, as well as merging the parameters
into a new VideoCaptureTarget type.

Bug: 1264547, 1259398
Change-Id: I75c9f6cd585582631cea17c9d2b67a9c823bd56c
Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/3273346
Commit-Queue: Jordan Bayles <jophba@chromium.org>
Reviewed-by: Daniel Cheng <dcheng@chromium.org>
Reviewed-by: Jonathan Ross <jonross@chromium.org>
Reviewed-by: Elad Alon <eladalon@chromium.org>
Reviewed-by: Scott Violet <sky@chromium.org>
Reviewed-by: Danil Somsikov <dsv@chromium.org>
Cr-Commit-Position: refs/heads/main@{#942784}
diff --git a/ash/services/recording/video_capture_params.cc b/ash/services/recording/video_capture_params.cc
index 1832e81..20675dd 100644
--- a/ash/services/recording/video_capture_params.cc
+++ b/ash/services/recording/video_capture_params.cc
@@ -121,12 +121,8 @@
     DCHECK_NE(frame_sink_id_, new_frame_sink_id);
 
     frame_sink_id_ = new_frame_sink_id;
-
-    auto sub_target =
-        subtree_capture_id_.is_valid()
-            ? viz::mojom::SubTarget::NewSubtreeCaptureId(subtree_capture_id_)
-            : nullptr;
-    capturer->ChangeTarget(frame_sink_id_, std::move(sub_target));
+    capturer->ChangeTarget(
+        viz::VideoCaptureTarget(frame_sink_id_, subtree_capture_id_));
 
     // If the movement to another display results in changes in the frame sink
     // size or DSF, OnVideoSizeMayHaveChanged() will be called by the below
@@ -293,12 +289,8 @@
   // TODO(afakhry): Discuss with //media/ team the implications of color space
   // conversions.
   capturer->SetFormat(media::PIXEL_FORMAT_I420, kColorSpace);
-
-  auto sub_target =
-      subtree_capture_id_.is_valid()
-          ? viz::mojom::SubTarget::NewSubtreeCaptureId(subtree_capture_id_)
-          : nullptr;
-  capturer->ChangeTarget(frame_sink_id_, std::move(sub_target));
+  capturer->ChangeTarget(
+      viz::VideoCaptureTarget(frame_sink_id_, subtree_capture_id_));
 }
 
 gfx::Rect VideoCaptureParams::GetVideoFrameVisibleRect(
diff --git a/components/viz/common/BUILD.gn b/components/viz/common/BUILD.gn
index 2ce3503..45bc802 100644
--- a/components/viz/common/BUILD.gn
+++ b/components/viz/common/BUILD.gn
@@ -293,6 +293,8 @@
     "surfaces/surface_info.h",
     "surfaces/surface_range.cc",
     "surfaces/surface_range.h",
+    "surfaces/video_capture_target.cc",
+    "surfaces/video_capture_target.h",
     "switches.cc",
     "switches.h",
     "traced_value.cc",
diff --git a/components/viz/common/surfaces/video_capture_target.cc b/components/viz/common/surfaces/video_capture_target.cc
new file mode 100644
index 0000000..0c67db64
--- /dev/null
+++ b/components/viz/common/surfaces/video_capture_target.cc
@@ -0,0 +1,47 @@
+// Copyright 2021 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "components/viz/common/surfaces/video_capture_target.h"
+
+#include "base/logging.h"
+
+namespace viz {
+
+namespace {
+bool SubTargetIsValid(const VideoCaptureSubTarget& sub_target) {
+  if (absl::holds_alternative<absl::monostate>(sub_target)) {
+    return true;
+  }
+  if (const auto* crop_id = absl::get_if<RegionCaptureCropId>(&sub_target)) {
+    return !crop_id->is_zero();
+  }
+  const auto* capture_id = absl::get_if<SubtreeCaptureId>(&sub_target);
+  DCHECK(capture_id);
+  return capture_id->is_valid();
+}
+
+}  // namespace
+
+VideoCaptureTarget::VideoCaptureTarget(FrameSinkId frame_sink_id)
+    : VideoCaptureTarget(frame_sink_id, VideoCaptureSubTarget()) {}
+VideoCaptureTarget::VideoCaptureTarget(FrameSinkId frame_sink_id,
+                                       VideoCaptureSubTarget sub_target)
+    : frame_sink_id(frame_sink_id),
+      sub_target(SubTargetIsValid(sub_target) ? sub_target
+                                              : VideoCaptureSubTarget{}) {
+  DCHECK(this->frame_sink_id.is_valid());
+}
+
+VideoCaptureTarget::VideoCaptureTarget() = default;
+VideoCaptureTarget::VideoCaptureTarget(const VideoCaptureTarget& other) =
+    default;
+VideoCaptureTarget::VideoCaptureTarget(VideoCaptureTarget&& other) = default;
+VideoCaptureTarget& VideoCaptureTarget::operator=(
+    const VideoCaptureTarget& other) = default;
+VideoCaptureTarget& VideoCaptureTarget::operator=(VideoCaptureTarget&& other) =
+    default;
+
+VideoCaptureTarget::~VideoCaptureTarget() = default;
+
+}  // namespace viz
diff --git a/components/viz/common/surfaces/video_capture_target.h b/components/viz/common/surfaces/video_capture_target.h
new file mode 100644
index 0000000..4a1e537
--- /dev/null
+++ b/components/viz/common/surfaces/video_capture_target.h
@@ -0,0 +1,65 @@
+// Copyright 2021 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef COMPONENTS_VIZ_COMMON_SURFACES_VIDEO_CAPTURE_TARGET_H_
+#define COMPONENTS_VIZ_COMMON_SURFACES_VIDEO_CAPTURE_TARGET_H_
+
+#include "base/token.h"
+#include "components/viz/common/surfaces/frame_sink_id.h"
+#include "components/viz/common/surfaces/region_capture_bounds.h"
+#include "components/viz/common/surfaces/subtree_capture_id.h"
+#include "components/viz/common/viz_common_export.h"
+#include "third_party/abseil-cpp/absl/types/variant.h"
+
+namespace viz {
+
+// Note about sub targets: subtree-capture and region-capture are mutually
+// exclusive. This is trivially guaranteed by subtree-capture only being
+// supported on window-capture, and region-capture only being supported on
+// tab-capture.
+using VideoCaptureSubTarget =
+    absl::variant<absl::monostate, SubtreeCaptureId, RegionCaptureCropId>;
+
+// All of the information necessary to select a target for capture.
+// If constructed, the |frame_sink_id| must be valid and |sub_target|
+// is optional. If not provided, this target is the root render pass of
+// the frame sink. If |sub_target| is a SubtreeCaptureId, this target is
+// a layer subtree under the root render pass. Else, if |sub_target| is
+// a RegionCaptureCropId, this target is the root render pass but only
+// a subset of the pixels are selected for capture.
+struct VIZ_COMMON_EXPORT VideoCaptureTarget {
+  explicit VideoCaptureTarget(FrameSinkId frame_sink_id);
+
+  // If an invalid sub target is provided, it will be internally converted to an
+  // absl::monostate, equivalent to calling the single argument constructor
+  // above.
+  VideoCaptureTarget(FrameSinkId frame_sink_id,
+                     VideoCaptureSubTarget capture_sub_target);
+
+  VideoCaptureTarget();
+  ~VideoCaptureTarget();
+  VideoCaptureTarget(const VideoCaptureTarget& other);
+  VideoCaptureTarget(VideoCaptureTarget&& other);
+  VideoCaptureTarget& operator=(const VideoCaptureTarget& other);
+  VideoCaptureTarget& operator=(VideoCaptureTarget&& other);
+
+  inline bool operator==(const VideoCaptureTarget& other) const {
+    return frame_sink_id == other.frame_sink_id &&
+           sub_target == other.sub_target;
+  }
+
+  inline bool operator!=(const VideoCaptureTarget& other) const {
+    return !(*this == other);
+  }
+
+  // The target frame sink id. Must be valid.
+  FrameSinkId frame_sink_id;
+
+  // The sub target.
+  VideoCaptureSubTarget sub_target;
+};
+
+}  // namespace viz
+
+#endif  // COMPONENTS_VIZ_COMMON_SURFACES_VIDEO_CAPTURE_TARGET_H_
diff --git a/components/viz/host/client_frame_sink_video_capturer.cc b/components/viz/host/client_frame_sink_video_capturer.cc
index ac652ed..b2b21d9 100644
--- a/components/viz/host/client_frame_sink_video_capturer.cc
+++ b/components/viz/host/client_frame_sink_video_capturer.cc
@@ -72,15 +72,11 @@
 }
 
 void ClientFrameSinkVideoCapturer::ChangeTarget(
-    const absl::optional<FrameSinkId>& frame_sink_id,
-    mojom::SubTargetPtr sub_target) {
+    const absl::optional<VideoCaptureTarget>& target) {
   DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
 
-  target_ = frame_sink_id;
-  sub_target_ = std::move(sub_target);
-
-  capturer_remote_->ChangeTarget(frame_sink_id,
-                                 sub_target_ ? sub_target_.Clone() : nullptr);
+  target_ = target;
+  capturer_remote_->ChangeTarget(target);
 }
 
 void ClientFrameSinkVideoCapturer::Start(
@@ -196,8 +192,7 @@
   if (auto_throttling_enabled_)
     capturer_remote_->SetAutoThrottlingEnabled(*auto_throttling_enabled_);
   if (target_) {
-    capturer_remote_->ChangeTarget(
-        target_, sub_target_ ? sub_target_->Clone() : nullptr);
+    capturer_remote_->ChangeTarget(target_.value());
   }
   for (Overlay* overlay : overlays_)
     overlay->EstablishConnection(capturer_remote_.get());
diff --git a/components/viz/host/client_frame_sink_video_capturer.h b/components/viz/host/client_frame_sink_video_capturer.h
index 6d319eae..210e8784 100644
--- a/components/viz/host/client_frame_sink_video_capturer.h
+++ b/components/viz/host/client_frame_sink_video_capturer.h
@@ -13,8 +13,7 @@
 #include "base/sequence_checker.h"
 #include "base/time/time.h"
 #include "components/viz/common/surfaces/frame_sink_id.h"
-#include "components/viz/common/surfaces/region_capture_bounds.h"
-#include "components/viz/common/surfaces/subtree_capture_id.h"
+#include "components/viz/common/surfaces/video_capture_target.h"
 #include "components/viz/host/viz_host_export.h"
 #include "media/base/video_types.h"
 #include "mojo/public/cpp/bindings/pending_receiver.h"
@@ -87,8 +86,7 @@
                                 const gfx::Size& max_size,
                                 bool use_fixed_aspect_ratio);
   void SetAutoThrottlingEnabled(bool enabled);
-  void ChangeTarget(const absl::optional<FrameSinkId>& frame_sink_id,
-                    mojom::SubTargetPtr sub_target);
+  void ChangeTarget(const absl::optional<VideoCaptureTarget>& target);
   void Stop();
   void RequestRefreshFrame();
 
@@ -155,8 +153,7 @@
   absl::optional<base::TimeDelta> min_size_change_period_;
   absl::optional<ResolutionConstraints> resolution_constraints_;
   absl::optional<bool> auto_throttling_enabled_;
-  absl::optional<FrameSinkId> target_;
-  mojom::SubTargetPtr sub_target_;
+  absl::optional<VideoCaptureTarget> target_;
   // Overlays are owned by the callers of CreateOverlay().
   std::vector<Overlay*> overlays_;
   bool is_started_ = false;
diff --git a/components/viz/service/BUILD.gn b/components/viz/service/BUILD.gn
index 972d8fc..f9d0c20 100644
--- a/components/viz/service/BUILD.gn
+++ b/components/viz/service/BUILD.gn
@@ -227,6 +227,7 @@
 
     # Note that dependency on //gpu/ipc/client is for GpuMemoryBufferImpl. This
     # dependency should not be in public_deps.
+    "//components/viz/common",
     "//gpu/ipc/client",
     "//gpu/ipc/common:common",
     "//gpu/ipc/common:surface_handle_type",
diff --git a/components/viz/service/frame_sinks/compositor_frame_sink_support.cc b/components/viz/service/frame_sinks/compositor_frame_sink_support.cc
index 04df254..71c33b3 100644
--- a/components/viz/service/frame_sinks/compositor_frame_sink_support.cc
+++ b/components/viz/service/frame_sinks/compositor_frame_sink_support.cc
@@ -24,6 +24,7 @@
 #include "components/viz/common/quads/compositor_render_pass.h"
 #include "components/viz/common/resources/bitmap_allocation.h"
 #include "components/viz/common/surfaces/surface_info.h"
+#include "components/viz/common/surfaces/video_capture_target.h"
 #include "components/viz/service/display/display.h"
 #include "components/viz/service/display/shared_bitmap_manager.h"
 #include "components/viz/service/frame_sinks/frame_sink_bundle_impl.h"
@@ -944,7 +945,7 @@
 }
 
 gfx::Rect CompositorFrameSinkSupport::GetCopyOutputRequestRegion(
-    const CapturableFrameSink::RegionSpecifier& specifier) const {
+    const VideoCaptureSubTarget& sub_target) const {
   if (!last_activated_surface_id_.is_valid()) {
     return {};
   }
@@ -957,21 +958,21 @@
   }
 
   // We will either have a subtree ID or a region capture crop_id, but not both.
-  if (absl::holds_alternative<RegionCaptureCropId>(specifier)) {
-    return GetCaptureBounds(absl::get<RegionCaptureCropId>(specifier));
+  if (absl::holds_alternative<RegionCaptureCropId>(sub_target)) {
+    return GetCaptureBounds(absl::get<RegionCaptureCropId>(sub_target));
   }
 
   // We can exit early if there is no subtree, otherwise we need to
   // intersect the bounds.
   const CompositorFrame& frame = current_surface->GetActiveFrame();
-  if (!absl::holds_alternative<SubtreeCaptureId>(specifier)) {
+  if (!absl::holds_alternative<SubtreeCaptureId>(sub_target)) {
     return gfx::Rect(frame.size_in_pixels());
   }
 
   // Now we know we don't have a crop_id and we do have a subtree ID.
   for (const auto& render_pass : frame.render_pass_list) {
     if (render_pass->subtree_capture_id ==
-        absl::get<SubtreeCaptureId>(specifier)) {
+        absl::get<SubtreeCaptureId>(sub_target)) {
       return render_pass->subtree_size.IsEmpty()
                  ? render_pass->output_rect
                  : gfx::Rect(render_pass->subtree_size);
diff --git a/components/viz/service/frame_sinks/compositor_frame_sink_support.h b/components/viz/service/frame_sinks/compositor_frame_sink_support.h
index 5360d22..2c2f504 100644
--- a/components/viz/service/frame_sinks/compositor_frame_sink_support.h
+++ b/components/viz/service/frame_sinks/compositor_frame_sink_support.h
@@ -22,6 +22,7 @@
 #include "components/viz/common/surfaces/region_capture_bounds.h"
 #include "components/viz/common/surfaces/surface_info.h"
 #include "components/viz/common/surfaces/surface_range.h"
+#include "components/viz/common/surfaces/video_capture_target.h"
 #include "components/viz/service/frame_sinks/begin_frame_tracker.h"
 #include "components/viz/service/frame_sinks/surface_resource_holder.h"
 #include "components/viz/service/frame_sinks/surface_resource_holder_client.h"
@@ -199,7 +200,7 @@
   void AttachCaptureClient(CapturableFrameSink::Client* client) override;
   void DetachCaptureClient(CapturableFrameSink::Client* client) override;
   gfx::Rect GetCopyOutputRequestRegion(
-      const CapturableFrameSink::RegionSpecifier& specifier) const override;
+      const VideoCaptureSubTarget& specifier) const override;
   void OnClientCaptureStarted() override;
   void OnClientCaptureStopped() override;
   void RequestCopyOfOutput(
diff --git a/components/viz/service/frame_sinks/compositor_frame_sink_support_unittest.cc b/components/viz/service/frame_sinks/compositor_frame_sink_support_unittest.cc
index 09bbe6a..62ec925 100644
--- a/components/viz/service/frame_sinks/compositor_frame_sink_support_unittest.cc
+++ b/components/viz/service/frame_sinks/compositor_frame_sink_support_unittest.cc
@@ -1632,8 +1632,8 @@
 
 TEST_F(CompositorFrameSinkSupportTest, GetCopyOutputRequestRegion) {
   // No surface with active frame.
-  EXPECT_EQ((gfx::Rect{}), support_->GetCopyOutputRequestRegion(
-                               CapturableFrameSink::RegionSpecifier()));
+  EXPECT_EQ((gfx::Rect{}),
+            support_->GetCopyOutputRequestRegion(VideoCaptureSubTarget()));
 
   // Surface with active frame but no capture identifier.
   ResourceId first_frame_ids[] = {ResourceId(1), ResourceId(2), ResourceId(3),
@@ -1641,8 +1641,7 @@
   SubmitCompositorFrameWithResources(first_frame_ids,
                                      base::size(first_frame_ids));
   EXPECT_EQ((gfx::Rect{0, 0, 20, 20}),
-            (support_->GetCopyOutputRequestRegion(
-                CapturableFrameSink::RegionSpecifier())));
+            (support_->GetCopyOutputRequestRegion(VideoCaptureSubTarget())));
 
   // Render pass with subtree size.
   const SurfaceId surface_id(support_->frame_sink_id(), local_surface_id_);
diff --git a/components/viz/service/frame_sinks/frame_sink_bundle_impl_unittest.cc b/components/viz/service/frame_sinks/frame_sink_bundle_impl_unittest.cc
index db33b44..fad5c884 100644
--- a/components/viz/service/frame_sinks/frame_sink_bundle_impl_unittest.cc
+++ b/components/viz/service/frame_sinks/frame_sink_bundle_impl_unittest.cc
@@ -120,12 +120,12 @@
   TestFrameSink(
       FrameSinkManagerImpl& manager,
       const FrameSinkId& id,
-      const absl::optional<FrameSinkId>& parent_id,
+      const FrameSinkId& parent_id,
       const absl::optional<FrameSinkBundleId>& bundle_id = absl::nullopt)
       : manager_(manager), id_(id) {
     manager_.RegisterFrameSinkId(id, /*report_activation=*/true);
-    if (parent_id) {
-      manager_.RegisterFrameSinkHierarchy(*parent_id, id);
+    if (parent_id.is_valid()) {
+      manager_.RegisterFrameSinkHierarchy(parent_id, id);
     }
     manager_.CreateCompositorFrameSink(
         id, bundle_id, frame_sink.BindNewPipeAndPassReceiver(),
diff --git a/components/viz/service/frame_sinks/frame_sink_manager_impl.cc b/components/viz/service/frame_sinks/frame_sink_manager_impl.cc
index 9860f8ae..6fa971d 100644
--- a/components/viz/service/frame_sinks/frame_sink_manager_impl.cc
+++ b/components/viz/service/frame_sinks/frame_sink_manager_impl.cc
@@ -403,7 +403,8 @@
   support_map_[frame_sink_id] = support;
 
   for (auto& capturer : video_capturers_) {
-    if (capturer->requested_target() == frame_sink_id)
+    if (capturer->target() &&
+        capturer->target()->frame_sink_id == frame_sink_id)
       capturer->SetResolvedTarget(support);
   }
 
@@ -423,7 +424,8 @@
     observer.OnDestroyedCompositorFrameSink(frame_sink_id);
 
   for (auto& capturer : video_capturers_) {
-    if (capturer->requested_target() == frame_sink_id)
+    if (capturer->target() &&
+        capturer->target()->frame_sink_id == frame_sink_id)
       capturer->OnTargetWillGoAway();
   }
 
diff --git a/components/viz/service/frame_sinks/video_capture/capturable_frame_sink.h b/components/viz/service/frame_sinks/video_capture/capturable_frame_sink.h
index 8fedddb..8808169 100644
--- a/components/viz/service/frame_sinks/video_capture/capturable_frame_sink.h
+++ b/components/viz/service/frame_sinks/video_capture/capturable_frame_sink.h
@@ -7,6 +7,7 @@
 
 #include "base/time/time.h"
 #include "components/viz/common/surfaces/region_capture_bounds.h"
+#include "components/viz/common/surfaces/video_capture_target.h"
 #include "components/viz/service/surfaces/pending_copy_output_request.h"
 #include "third_party/abseil-cpp/absl/types/variant.h"
 #include "ui/gfx/geometry/size.h"
@@ -64,10 +65,8 @@
   // its associated bounds are set to empty or could not be found.
   // NOTE: only one of |subtree_id| or |crop_id| should be set and valid, not
   // both.
-  using RegionSpecifier =
-      absl::variant<absl::monostate, SubtreeCaptureId, RegionCaptureCropId>;
   virtual gfx::Rect GetCopyOutputRequestRegion(
-      const RegionSpecifier& specifier) const = 0;
+      const VideoCaptureSubTarget& sub_target) const = 0;
 
   // Called when a video capture client starts or stops capturing.
   virtual void OnClientCaptureStarted() = 0;
diff --git a/components/viz/service/frame_sinks/video_capture/frame_sink_video_capturer_impl.cc b/components/viz/service/frame_sinks/video_capture/frame_sink_video_capturer_impl.cc
index bf70c6f..d7ad719 100644
--- a/components/viz/service/frame_sinks/video_capture/frame_sink_video_capturer_impl.cc
+++ b/components/viz/service/frame_sinks/video_capture/frame_sink_video_capturer_impl.cc
@@ -229,26 +229,16 @@
 }
 
 void FrameSinkVideoCapturerImpl::ChangeTarget(
-    const absl::optional<FrameSinkId>& frame_sink_id,
-    mojom::SubTargetPtr sub_target) {
+    const absl::optional<VideoCaptureTarget>& target) {
   DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
+  target_ = target;
 
-  if (frame_sink_id) {
-    requested_target_ = *frame_sink_id;
-    region_specifier_ =
-        !sub_target ? CapturableFrameSink::RegionSpecifier()
-                    : sub_target->is_subtree_capture_id()
-                          ? sub_target->get_subtree_capture_id()
-                          : sub_target->is_region_capture_crop_id()
-                                ? sub_target->get_region_capture_crop_id()
-                                : CapturableFrameSink::RegionSpecifier();
-    SetResolvedTarget(
-        frame_sink_manager_->FindCapturableFrameSink(requested_target_));
-  } else {
-    requested_target_ = FrameSinkId();
-    region_specifier_ = CapturableFrameSink::RegionSpecifier();
-    SetResolvedTarget(nullptr);
+  CapturableFrameSink* resolved_target = nullptr;
+  if (target_) {
+    resolved_target =
+        frame_sink_manager_->FindCapturableFrameSink(target_->frame_sink_id);
   }
+  SetResolvedTarget(resolved_target);
 }
 
 void FrameSinkVideoCapturerImpl::Start(
@@ -360,8 +350,9 @@
   }
 
   // Detect whether the source size changed before attempting capture.
+  DCHECK(target_);
   const gfx::Rect capture_region =
-      resolved_target_->GetCopyOutputRequestRegion(region_specifier_);
+      resolved_target_->GetCopyOutputRequestRegion(target_->sub_target);
   if (capture_region.IsEmpty()) {
     // If the capture region is empty, it means one of two things: the first
     // frame has not been composited yet or the current region selected for
@@ -397,15 +388,16 @@
   DCHECK(!damage_rect.IsEmpty());
   DCHECK(!expected_display_time.is_null());
   DCHECK(resolved_target_);
+  DCHECK(target_);
 
   const gfx::Rect capture_region =
-      resolved_target_->GetCopyOutputRequestRegion(region_specifier_);
+      resolved_target_->GetCopyOutputRequestRegion(target_->sub_target);
   if (capture_region.IsEmpty()) {
     return;
   }
 
   if (capture_region.size() == oracle_->source_size()) {
-    if (!absl::holds_alternative<absl::monostate>(region_specifier_)) {
+    if (!absl::holds_alternative<absl::monostate>(target_->sub_target)) {
       // The damage_rect may not be in the same coordinate space when we have
       // a valid request subtree identifier, so to be safe we just invalidate
       // the entire source.
@@ -485,6 +477,7 @@
     const CompositorFrameMetadata& frame_metadata) {
   DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
   DCHECK(resolved_target_);
+  DCHECK(target_);
 
   // Consult the oracle to determine whether this frame should be captured.
   if (oracle_->ObserveEventAndDecideCapture(event, damage_rect, event_time)) {
@@ -678,7 +671,7 @@
   // size of the capture region. If the capture region is empty, we shouldn't
   // capture.
   const gfx::Rect capture_region =
-      resolved_target_->GetCopyOutputRequestRegion(region_specifier_);
+      resolved_target_->GetCopyOutputRequestRegion(target_->sub_target);
   if (capture_region.IsEmpty()) {
     return;
   }
@@ -724,8 +717,8 @@
   }
 
   const SubtreeCaptureId subtree_id =
-      absl::holds_alternative<SubtreeCaptureId>(region_specifier_)
-          ? absl::get<SubtreeCaptureId>(region_specifier_)
+      absl::holds_alternative<SubtreeCaptureId>(target_->sub_target)
+          ? absl::get<SubtreeCaptureId>(target_->sub_target)
           : SubtreeCaptureId();
 
   resolved_target_->RequestCopyOfOutput(
diff --git a/components/viz/service/frame_sinks/video_capture/frame_sink_video_capturer_impl.h b/components/viz/service/frame_sinks/video_capture/frame_sink_video_capturer_impl.h
index 591cdf8..990ace8 100644
--- a/components/viz/service/frame_sinks/video_capture/frame_sink_video_capturer_impl.h
+++ b/components/viz/service/frame_sinks/video_capture/frame_sink_video_capturer_impl.h
@@ -21,7 +21,7 @@
 #include "base/unguessable_token.h"
 #include "components/viz/common/quads/compositor_frame_metadata.h"
 #include "components/viz/common/surfaces/frame_sink_id.h"
-#include "components/viz/common/surfaces/subtree_capture_id.h"
+#include "components/viz/common/surfaces/video_capture_target.h"
 #include "components/viz/service/frame_sinks/video_capture/capturable_frame_sink.h"
 #include "components/viz/service/frame_sinks/video_capture/in_flight_frame_delivery.h"
 #include "components/viz/service/frame_sinks/video_capture/shared_memory_video_frame_pool.h"
@@ -92,7 +92,7 @@
   // The currently-requested frame sink for capture. The frame sink manager
   // calls this when it learns of a new CapturableFrameSink to see if the target
   // can be resolved.
-  const FrameSinkId& requested_target() const { return requested_target_; }
+  const absl::optional<VideoCaptureTarget>& target() const { return target_; }
 
   // Sets the resolved target, detaching this capturer from the previous target
   // (if any), and attaching to the new target. This is called by the frame sink
@@ -115,8 +115,7 @@
                                 const gfx::Size& max_size,
                                 bool use_fixed_aspect_ratio) final;
   void SetAutoThrottlingEnabled(bool enabled) final;
-  void ChangeTarget(const absl::optional<FrameSinkId>& frame_sink_id,
-                    mojom::SubTargetPtr sub_target) final;
+  void ChangeTarget(const absl::optional<VideoCaptureTarget>& target) final;
   void Start(mojo::PendingRemote<mojom::FrameSinkVideoConsumer> consumer) final;
   void Stop() final;
   void RequestRefreshFrame() final;
@@ -261,13 +260,8 @@
   const std::unique_ptr<media::VideoCaptureOracle> oracle_;
 
   // The target requested by the client, as provided in the last call to
-  // ChangeTarget().
-  FrameSinkId requested_target_;
-
-  // A specifier that indicates what region of the layer should be captured.
-  // If not valid, then the root render pass of the target frame sink should
-  // be captured.
-  CapturableFrameSink::RegionSpecifier region_specifier_;
+  // ChangeTarget(). May be nullopt if no target is currently set.
+  absl::optional<VideoCaptureTarget> target_;
 
   // The resolved target of video capture, or null if the requested target does
   // not yet exist (or no longer exists).
diff --git a/components/viz/service/frame_sinks/video_capture/frame_sink_video_capturer_impl_unittest.cc b/components/viz/service/frame_sinks/video_capture/frame_sink_video_capturer_impl_unittest.cc
index 09499a3..de8ca7c 100644
--- a/components/viz/service/frame_sinks/video_capture/frame_sink_video_capturer_impl_unittest.cc
+++ b/components/viz/service/frame_sinks/video_capture/frame_sink_video_capturer_impl_unittest.cc
@@ -20,6 +20,7 @@
 #include "components/viz/common/frame_sinks/copy_output_result.h"
 #include "components/viz/common/frame_sinks/copy_output_util.h"
 #include "components/viz/common/surfaces/subtree_capture_id.h"
+#include "components/viz/common/surfaces/video_capture_target.h"
 #include "components/viz/service/frame_sinks/video_capture/frame_sink_video_capturer_manager.h"
 #include "media/base/limits.h"
 #include "media/base/video_util.h"
@@ -258,17 +259,17 @@
   }
 
   gfx::Rect GetCopyOutputRequestRegion(
-      const CapturableFrameSink::RegionSpecifier& specifier) const override {
-    if (absl::holds_alternative<RegionCaptureCropId>(specifier)) {
+      const VideoCaptureSubTarget& sub_target) const override {
+    if (absl::holds_alternative<RegionCaptureCropId>(sub_target)) {
       current_capture_id_ = SubtreeCaptureId();
-      current_crop_id_ = absl::get<RegionCaptureCropId>(specifier);
+      current_crop_id_ = absl::get<RegionCaptureCropId>(sub_target);
       if (!current_crop_id_.is_zero()) {
         return crop_bounds_;
       }
       return {};
     }
-    if (absl::holds_alternative<SubtreeCaptureId>(specifier)) {
-      current_capture_id_ = absl::get<SubtreeCaptureId>(specifier);
+    if (absl::holds_alternative<SubtreeCaptureId>(sub_target)) {
+      current_capture_id_ = absl::get<SubtreeCaptureId>(sub_target);
       current_crop_id_ = RegionCaptureCropId();
       if (current_capture_id_.is_valid()) {
         return capture_bounds_;
@@ -582,9 +583,9 @@
   EXPECT_CALL(frame_sink_manager_, FindCapturableFrameSink(kFrameSinkId))
       .WillRepeatedly(Return(&frame_sink_));
 
-  EXPECT_EQ(FrameSinkId(), capturer_->requested_target());
-  capturer_->ChangeTarget(kFrameSinkId, nullptr);
-  EXPECT_EQ(kFrameSinkId, capturer_->requested_target());
+  EXPECT_FALSE(capturer_->target());
+  capturer_->ChangeTarget(VideoCaptureTarget(kFrameSinkId));
+  EXPECT_EQ(kFrameSinkId, capturer_->target()->frame_sink_id);
   EXPECT_EQ(capturer_.get(), frame_sink_.attached_client());
 }
 
@@ -594,9 +595,9 @@
   EXPECT_CALL(frame_sink_manager_, FindCapturableFrameSink(kFrameSinkId))
       .WillRepeatedly(Return(nullptr));
 
-  EXPECT_EQ(FrameSinkId(), capturer_->requested_target());
-  capturer_->ChangeTarget(kFrameSinkId, nullptr);
-  EXPECT_EQ(kFrameSinkId, capturer_->requested_target());
+  EXPECT_FALSE(capturer_->target());
+  capturer_->ChangeTarget(VideoCaptureTarget(kFrameSinkId));
+  EXPECT_EQ(kFrameSinkId, capturer_->target()->frame_sink_id);
   EXPECT_EQ(nullptr, frame_sink_.attached_client());
 
   capturer_->SetResolvedTarget(&frame_sink_);
@@ -630,7 +631,7 @@
 
   // Now, set the target. As it resolves, the capturer will immediately attempt
   // a refresh capture, which will cancel the timer and trigger a copy request.
-  capturer_->ChangeTarget(kFrameSinkId, nullptr);
+  capturer_->ChangeTarget(VideoCaptureTarget(kFrameSinkId));
   EXPECT_EQ(1, frame_sink_.num_copy_results());
   EXPECT_FALSE(IsRefreshRetryTimerRunning());
 
@@ -646,7 +647,7 @@
   EXPECT_CALL(frame_sink_manager_, FindCapturableFrameSink(kFrameSinkId))
       .WillRepeatedly(Return(&frame_sink_));
 
-  capturer_->ChangeTarget(kFrameSinkId, nullptr);
+  capturer_->ChangeTarget(VideoCaptureTarget(kFrameSinkId));
   EXPECT_FALSE(IsRefreshRetryTimerRunning());
 
   MockConsumer consumer;
@@ -741,7 +742,7 @@
   EXPECT_CALL(frame_sink_manager_, FindCapturableFrameSink(kFrameSinkId))
       .WillRepeatedly(Return(&frame_sink_));
 
-  capturer_->ChangeTarget(kFrameSinkId, nullptr);
+  capturer_->ChangeTarget(VideoCaptureTarget(kFrameSinkId));
 
   NiceMock<MockConsumer> consumer;
   StartCapture(&consumer);
@@ -839,7 +840,7 @@
   EXPECT_CALL(frame_sink_manager_, FindCapturableFrameSink(kFrameSinkId))
       .WillRepeatedly(Return(&frame_sink_));
 
-  capturer_->ChangeTarget(kFrameSinkId, nullptr);
+  capturer_->ChangeTarget(VideoCaptureTarget(kFrameSinkId));
 
   NiceMock<MockConsumer> consumer;
   StartCapture(&consumer);
@@ -896,7 +897,7 @@
   EXPECT_CALL(frame_sink_manager_, FindCapturableFrameSink(kFrameSinkId))
       .WillRepeatedly(Return(&frame_sink_));
 
-  capturer_->ChangeTarget(kFrameSinkId, nullptr);
+  capturer_->ChangeTarget(VideoCaptureTarget(kFrameSinkId));
 
   // Start capturing to the first consumer.
   MockConsumer consumer;
@@ -983,7 +984,7 @@
   EXPECT_CALL(frame_sink_manager_, FindCapturableFrameSink(kFrameSinkId))
       .WillRepeatedly(Return(&frame_sink_));
 
-  capturer_->ChangeTarget(kFrameSinkId, nullptr);
+  capturer_->ChangeTarget(VideoCaptureTarget(kFrameSinkId));
 
   MockConsumer consumer;
   const int num_refresh_frames = 2;  // Initial, plus later refresh.
@@ -1039,7 +1040,7 @@
   EXPECT_CALL(frame_sink_manager_, FindCapturableFrameSink(kFrameSinkId))
       .WillRepeatedly(Return(&frame_sink_));
 
-  capturer_->ChangeTarget(kFrameSinkId, nullptr);
+  capturer_->ChangeTarget(VideoCaptureTarget(kFrameSinkId));
 
   MockConsumer consumer;
   constexpr int num_refresh_frames = 3;  // Initial, plus two refreshes after
@@ -1136,7 +1137,7 @@
 TEST_F(FrameSinkVideoCapturerTest, CompositorFrameMetadataReachesConsumer) {
   EXPECT_CALL(frame_sink_manager_, FindCapturableFrameSink(kFrameSinkId))
       .WillRepeatedly(Return(&frame_sink_));
-  capturer_->ChangeTarget(kFrameSinkId, nullptr);
+  capturer_->ChangeTarget(VideoCaptureTarget(kFrameSinkId));
 
   MockConsumer consumer;
   // Initial refresh frame for starting capture, plus later refresh.
@@ -1196,7 +1197,7 @@
 TEST_F(FrameSinkVideoCapturerTest, DeliversUpdateRectAndCaptureCounter) {
   EXPECT_CALL(frame_sink_manager_, FindCapturableFrameSink(kFrameSinkId))
       .WillRepeatedly(Return(&frame_sink_));
-  capturer_->ChangeTarget(kFrameSinkId, nullptr);
+  capturer_->ChangeTarget(VideoCaptureTarget(kFrameSinkId));
 
   MockConsumer consumer;
   StartCapture(&consumer);
@@ -1311,7 +1312,7 @@
 TEST_F(FrameSinkVideoCapturerTest, CaptureCounterSkipsWhenFramesAreDropped) {
   EXPECT_CALL(frame_sink_manager_, FindCapturableFrameSink(kFrameSinkId))
       .WillRepeatedly(Return(&frame_sink_));
-  capturer_->ChangeTarget(kFrameSinkId, nullptr);
+  capturer_->ChangeTarget(VideoCaptureTarget(kFrameSinkId));
 
   MockConsumer consumer;
   StartCapture(&consumer);
@@ -1363,7 +1364,7 @@
   EXPECT_CALL(frame_sink_manager_, FindCapturableFrameSink(kFrameSinkId))
       .WillRepeatedly(Return(&frame_sink_));
 
-  capturer_->ChangeTarget(kFrameSinkId, nullptr);
+  capturer_->ChangeTarget(VideoCaptureTarget(kFrameSinkId));
   EXPECT_EQ(frame_sink_.number_clients_capturing(), 0);
 
   // Start capturing. frame_sink_ should now have one client capturing.
@@ -1380,14 +1381,13 @@
   SwitchToSizeSet(kSizeSets[4]);
   EXPECT_CALL(frame_sink_manager_, FindCapturableFrameSink(kFrameSinkId))
       .WillRepeatedly(Return(&frame_sink_));
-  capturer_->ChangeTarget(kFrameSinkId, nullptr);
+  capturer_->ChangeTarget(VideoCaptureTarget(kFrameSinkId));
   EXPECT_EQ(frame_sink_.number_clients_capturing(), 0);
 
   static const auto kCropId = RegionCaptureCropId::CreateRandom();
   static const gfx::Rect kCropBounds{1, 2, 640, 478};
   frame_sink_.set_crop_bounds(kCropBounds);
-  capturer_->ChangeTarget(kFrameSinkId,
-                          mojom::SubTarget::NewRegionCaptureCropId(kCropId));
+  capturer_->ChangeTarget(VideoCaptureTarget(kFrameSinkId, kCropId));
 
   // Start capturing. frame_sink_ should now have one client capturing.
   NiceMock<MockConsumer> consumer;
@@ -1406,8 +1406,7 @@
   static const gfx::Rect kCaptureBounds{1, 2, 1024, 768};
   static const SubtreeCaptureId kCaptureId{1234567u};
   frame_sink_.set_capture_bounds(kCaptureBounds);
-  capturer_->ChangeTarget(kFrameSinkId,
-                          mojom::SubTarget::NewSubtreeCaptureId(kCaptureId));
+  capturer_->ChangeTarget(VideoCaptureTarget(kFrameSinkId, kCaptureId));
 
   // Start capturing. frame_sink_ should now have one client capturing.
   NiceMock<MockConsumer> consumer;
@@ -1427,7 +1426,7 @@
   // The default cause is a target with no sub target, passed as nullptr. Since
   // the SubTarget is a mojom variant, the default SubTarget::New() is actually
   // a zero value subtree capture identifier.
-  capturer_->ChangeTarget(kFrameSinkId, nullptr);
+  capturer_->ChangeTarget(VideoCaptureTarget(kFrameSinkId));
   EXPECT_EQ(frame_sink_.number_clients_capturing(), 0);
 
   // Start capturing. frame_sink_ should now have one client capturing.
diff --git a/content/browser/devtools/devtools_video_consumer.cc b/content/browser/devtools/devtools_video_consumer.cc
index 8098d0e..513bd8de 100644
--- a/content/browser/devtools/devtools_video_consumer.cc
+++ b/content/browser/devtools/devtools_video_consumer.cc
@@ -86,11 +86,7 @@
     const viz::FrameSinkId& frame_sink_id) {
   frame_sink_id_ = frame_sink_id;
   if (capturer_) {
-    capturer_->ChangeTarget(
-        frame_sink_id_.is_valid()
-            ? absl::make_optional<viz::FrameSinkId>(frame_sink_id_)
-            : absl::nullopt,
-        nullptr);
+    capturer_->ChangeTarget(viz::VideoCaptureTarget(frame_sink_id_));
   }
 }
 
@@ -132,7 +128,7 @@
                                       kDefaultUseFixedAspectRatio);
   capturer_->SetFormat(pixel_format_, color_space_);
   if (frame_sink_id_.is_valid())
-    capturer_->ChangeTarget(frame_sink_id_, nullptr);
+    capturer_->ChangeTarget(viz::VideoCaptureTarget(frame_sink_id_));
 
   capturer_->Start(this);
 }
diff --git a/content/browser/devtools/devtools_video_consumer_unittest.cc b/content/browser/devtools/devtools_video_consumer_unittest.cc
index bb20f060..6ed566ef 100644
--- a/content/browser/devtools/devtools_video_consumer_unittest.cc
+++ b/content/browser/devtools/devtools_video_consumer_unittest.cc
@@ -85,9 +85,9 @@
                     bool use_fixed_aspect_ratio));
   // This is never called.
   MOCK_METHOD1(SetAutoThrottlingEnabled, void(bool));
-  void ChangeTarget(const absl::optional<viz::FrameSinkId>& frame_sink_id,
-                    viz::mojom::SubTargetPtr sub_target) final {
-    frame_sink_id_ = frame_sink_id ? *frame_sink_id : viz::FrameSinkId();
+  void ChangeTarget(
+      const absl::optional<viz::VideoCaptureTarget>& target) final {
+    frame_sink_id_ = target ? target->frame_sink_id : viz::FrameSinkId();
     MockChangeTarget(frame_sink_id_);
   }
   MOCK_METHOD1(MockChangeTarget, void(const viz::FrameSinkId& frame_sink_id));
diff --git a/content/browser/media/capture/aura_window_video_capture_device.cc b/content/browser/media/capture/aura_window_video_capture_device.cc
index 90fc150..3ff1aea 100644
--- a/content/browser/media/capture/aura_window_video_capture_device.cc
+++ b/content/browser/media/capture/aura_window_video_capture_device.cc
@@ -81,17 +81,19 @@
     DCHECK(!target_window_);
 
     target_window_ = DesktopMediaID::GetNativeWindowById(source_id);
-    FrameSinkVideoCaptureDevice::VideoCaptureTarget target;
-    if (target_window_) {
-      target.frame_sink_id = target_window_->GetRootWindow()->GetFrameSinkId();
+    if (target_window_ &&
+        target_window_->GetRootWindow()->GetFrameSinkId().is_valid()) {
+      target_ = viz::VideoCaptureTarget(
+          target_window_->GetRootWindow()->GetFrameSinkId());
       if (!target_window_->IsRootWindow()) {
         capture_request_ = target_window_->MakeWindowCapturable();
-        target.subtree_capture_id = capture_request_.GetCaptureId();
+        target_->sub_target = capture_request_.GetCaptureId();
       }
+    } else {
+      target_ = absl::nullopt;
     }
 
-    if (target.frame_sink_id.is_valid()) {
-      target_ = target;
+    if (target_) {
       video_capture_lock_ = target_window_->GetHost()->CreateVideoCaptureLock();
 #if BUILDFLAG(IS_CHROMEOS_ASH)
       force_visible_.emplace(target_window_);
@@ -100,7 +102,7 @@
       device_task_runner_->PostTask(
           FROM_HERE,
           base::BindOnce(&FrameSinkVideoCaptureDevice::OnTargetChanged, device_,
-                         std::move(target)));
+                         target_));
       // Note: The MouseCursorOverlayController runs on the UI thread. It's also
       // important that SetTargetView() be called in the current stack while
       // |target_window_| is known to be a valid pointer.
@@ -144,12 +146,13 @@
 
     // Since the window is not destroyed, only re-parented, we can keep the
     // same subtree ID and only update the FrameSinkId of the target.
-    if (new_frame_sink_id != target_.frame_sink_id) {
-      target_.frame_sink_id = new_frame_sink_id;
+    DCHECK(target_);
+    if (new_frame_sink_id != target_->frame_sink_id) {
+      target_->frame_sink_id = new_frame_sink_id;
       device_task_runner_->PostTask(
           FROM_HERE,
           base::BindOnce(&FrameSinkVideoCaptureDevice::OnTargetChanged, device_,
-                         target_));
+                         target_.value()));
     }
   }
 #endif
@@ -173,7 +176,7 @@
 #endif
 
   aura::ScopedWindowCaptureRequest capture_request_;
-  FrameSinkVideoCaptureDevice::VideoCaptureTarget target_;
+  absl::optional<viz::VideoCaptureTarget> target_;
 
   std::unique_ptr<aura::WindowTreeHost::VideoCaptureLock> video_capture_lock_;
 };
diff --git a/content/browser/media/capture/frame_sink_video_capture_device.cc b/content/browser/media/capture/frame_sink_video_capture_device.cc
index 5287252f8a..9dbbfad4 100644
--- a/content/browser/media/capture/frame_sink_video_capture_device.cc
+++ b/content/browser/media/capture/frame_sink_video_capture_device.cc
@@ -65,23 +65,6 @@
   GetDeviceService().BindWakeLockProvider(std::move(receiver));
 }
 
-viz::mojom::SubTargetPtr ToSubTargetPtr(
-    const FrameSinkVideoCaptureDevice::VideoCaptureTarget& target) {
-  // Recall that either |subtree_capture_id| or |crop_id| is set,
-  // or neither, but never both. This was verified in |target|'s ctor,
-  // but is reiterated here for clarity's sake.
-  DCHECK(!target.subtree_capture_id.is_valid() || target.crop_id.is_zero());
-
-  if (target.subtree_capture_id.is_valid()) {
-    return viz::mojom::SubTarget::NewSubtreeCaptureId(
-        target.subtree_capture_id);
-  }
-  if (!target.crop_id.is_zero()) {
-    return viz::mojom::SubTarget::NewRegionCaptureCropId(target.crop_id);
-  }
-  return nullptr;
-}
-
 }  // namespace
 
 #if !defined(OS_ANDROID)
@@ -137,8 +120,8 @@
                                       constraints.max_frame_size,
                                       constraints.fixed_aspect_ratio);
 
-  if (target_.frame_sink_id.is_valid()) {
-    capturer_->ChangeTarget(target_.frame_sink_id, ToSubTargetPtr(target_));
+  if (target_) {
+    capturer_->ChangeTarget(target_);
   }
 
 #if !defined(OS_ANDROID)
@@ -325,22 +308,17 @@
 }
 
 void FrameSinkVideoCaptureDevice::OnTargetChanged(
-    const FrameSinkVideoCaptureDevice::VideoCaptureTarget& target) {
+    const absl::optional<viz::VideoCaptureTarget>& target) {
   DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
   target_ = target;
   if (capturer_) {
-    capturer_->ChangeTarget(
-        target_.frame_sink_id.is_valid()
-            ? absl::make_optional<viz::FrameSinkId>(target_.frame_sink_id)
-            : absl::nullopt,
-        ToSubTargetPtr(target_));
+    capturer_->ChangeTarget(target_);
   }
 }
 
 void FrameSinkVideoCaptureDevice::OnTargetPermanentlyLost() {
   DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
-
-  OnTargetChanged(VideoCaptureTarget());
+  OnTargetChanged(absl::nullopt);
   OnFatalError("Capture target has been permanently lost.");
 }
 
diff --git a/content/browser/media/capture/frame_sink_video_capture_device.h b/content/browser/media/capture/frame_sink_video_capture_device.h
index 5fd266be..c99d784 100644
--- a/content/browser/media/capture/frame_sink_video_capture_device.h
+++ b/content/browser/media/capture/frame_sink_video_capture_device.h
@@ -14,9 +14,9 @@
 #include "base/check.h"
 #include "base/memory/weak_ptr.h"
 #include "base/sequence_checker.h"
-#include "base/token.h"
 #include "build/build_config.h"
 #include "components/viz/common/surfaces/frame_sink_id.h"
+#include "components/viz/common/surfaces/video_capture_target.h"
 #include "components/viz/host/client_frame_sink_video_capturer.h"
 #include "content/common/content_export.h"
 #include "content/public/browser/browser_thread.h"
@@ -29,6 +29,7 @@
 #include "mojo/public/cpp/bindings/pending_remote.h"
 #include "mojo/public/cpp/bindings/remote.h"
 #include "services/device/public/mojom/wake_lock.mojom.h"
+#include "services/viz/public/cpp/compositing/video_capture_target_mojom_traits.h"
 #include "third_party/abseil-cpp/absl/types/optional.h"
 
 namespace content {
@@ -97,48 +98,11 @@
   void OnStopped() final;
   void OnLog(const std::string& message) final;
 
-  // All of the information necessary to select a target for capture.
-  struct VideoCaptureTarget {
-    VideoCaptureTarget() = default;
-    VideoCaptureTarget(viz::FrameSinkId frame_sink_id,
-                       viz::SubtreeCaptureId subtree_capture_id,
-                       const base::Token& crop_id)
-        : frame_sink_id(frame_sink_id),
-          subtree_capture_id(subtree_capture_id),
-          crop_id(crop_id) {
-      // Subtree-capture and region-capture are mutually exclusive.
-      // This is trivially guaranteed by subtree-capture only being supported
-      // on Aura window-capture, and region-capture only being supported on
-      // tab-capture.
-      DCHECK(!subtree_capture_id.is_valid() || crop_id.is_zero());
-    }
-
-    // The target frame sink id.
-    viz::FrameSinkId frame_sink_id;
-
-    // The subtree capture identifier--may be default initialized to indicate
-    // that the entire frame sink (defined by |frame_sink_id|) should be
-    // captured.
-    viz::SubtreeCaptureId subtree_capture_id;
-
-    // If |crop_id| is non-zero, it indicates that the video should be
-    // cropped to coordinates identified by it.
-    base::Token crop_id;
-
-    inline bool operator==(const VideoCaptureTarget& other) const {
-      return frame_sink_id == other.frame_sink_id &&
-             subtree_capture_id == other.subtree_capture_id &&
-             crop_id == other.crop_id;
-    }
-
-    inline bool operator!=(const VideoCaptureTarget& other) const {
-      return !(*this == other);
-    }
-  };
-
   // These are called to notify when the capture target has changed or was
-  // permanently lost.
-  virtual void OnTargetChanged(const VideoCaptureTarget& target);
+  // permanently lost. NOTE: a target can be temporarily absl::nullopt without
+  // being permanently lost.
+  virtual void OnTargetChanged(
+      const absl::optional<viz::VideoCaptureTarget>& target);
   virtual void OnTargetPermanentlyLost();
 
  protected:
@@ -188,7 +152,7 @@
   // Current capture target. This is cached to resolve a race where
   // OnTargetChanged() can be called before the |capturer_| is created in
   // OnCapturerCreated().
-  VideoCaptureTarget target_;
+  absl::optional<viz::VideoCaptureTarget> target_;
 
   // The requested format, rate, and other capture constraints.
   media::VideoCaptureParams capture_params_;
diff --git a/content/browser/media/capture/frame_sink_video_capture_device_unittest.cc b/content/browser/media/capture/frame_sink_video_capture_device_unittest.cc
index 6c1c06b..6f2d2fb 100644
--- a/content/browser/media/capture/frame_sink_video_capture_device_unittest.cc
+++ b/content/browser/media/capture/frame_sink_video_capture_device_unittest.cc
@@ -110,20 +110,13 @@
                     const gfx::Size& max_size,
                     bool use_fixed_aspect_ratio));
   MOCK_METHOD1(SetAutoThrottlingEnabled, void(bool));
-  void ChangeTarget(const absl::optional<viz::FrameSinkId>& frame_sink_id,
-                    viz::mojom::SubTargetPtr sub_target) final {
+  void ChangeTarget(
+      const absl::optional<viz::VideoCaptureTarget>& target) final {
     DCHECK_NOT_ON_DEVICE_THREAD();
-    viz::SubtreeCaptureId subtree_id;
-    if (sub_target && sub_target->is_subtree_capture_id()) {
-      subtree_id = sub_target->get_subtree_capture_id();
-    }
-    MockChangeTarget(FrameSinkVideoCaptureDevice::VideoCaptureTarget(
-        frame_sink_id.value_or(viz::FrameSinkId{}), subtree_id,
-        /*crop_id=*/base::Token()));
+    MockChangeTarget(target);
   }
-  MOCK_METHOD1(
-      MockChangeTarget,
-      void(const FrameSinkVideoCaptureDevice::VideoCaptureTarget& target));
+  MOCK_METHOD1(MockChangeTarget,
+               void(const absl::optional<viz::VideoCaptureTarget>& target));
   void Start(
       mojo::PendingRemote<viz::mojom::FrameSinkVideoConsumer> consumer) final {
     DCHECK_NOT_ON_DEVICE_THREAD();
@@ -351,10 +344,10 @@
     EXPECT_CALL(capturer_, SetMinCapturePeriod(kMinCapturePeriod));
     EXPECT_CALL(capturer_,
                 SetResolutionConstraints(kResolution, kResolution, _));
-    FrameSinkVideoCaptureDevice::VideoCaptureTarget target(
-        viz::FrameSinkId{1, 1}, viz::SubtreeCaptureId(),
-        /*crop_id=*/base::Token());
-    EXPECT_CALL(capturer_, MockChangeTarget(target));
+    const viz::VideoCaptureTarget target(viz::FrameSinkId{1, 1});
+    EXPECT_CALL(
+        capturer_,
+        MockChangeTarget(absl::optional<viz::VideoCaptureTarget>(target)));
     EXPECT_CALL(capturer_, MockStart(NotNull()));
 
     EXPECT_FALSE(capturer_.is_bound());
@@ -602,9 +595,8 @@
   // consumption, unbind the capturer, log an error with the VideoFrameReceiver,
   // and destroy the VideoFrameReceiver.
   {
-    EXPECT_CALL(
-        capturer_,
-        MockChangeTarget(FrameSinkVideoCaptureDevice::VideoCaptureTarget()));
+    EXPECT_CALL(capturer_,
+                MockChangeTarget(absl::optional<viz::VideoCaptureTarget>()));
     EXPECT_CALL(capturer_, MockStop());
     POST_DEVICE_METHOD_CALL0(OnTargetPermanentlyLost);
     WAIT_FOR_DEVICE_TASKS();
diff --git a/content/browser/media/capture/views_widget_video_capture_device_mac.cc b/content/browser/media/capture/views_widget_video_capture_device_mac.cc
index 8c2ce59..e1747db 100644
--- a/content/browser/media/capture/views_widget_video_capture_device_mac.cc
+++ b/content/browser/media/capture/views_widget_video_capture_device_mac.cc
@@ -55,9 +55,7 @@
           FROM_HERE,
           base::BindOnce(
               &FrameSinkVideoCaptureDevice::OnTargetChanged, device_,
-              FrameSinkVideoCaptureDevice::VideoCaptureTarget(
-                  scoped_cg_window_id_->GetFrameSinkId(),
-                  viz::SubtreeCaptureId(), /*crop_id=*/base::Token())));
+              viz::VideoCaptureTarget(scoped_cg_window_id_->GetFrameSinkId())));
     } else {
       // It is entirely possible (although unlikely) that the window
       // corresponding to |cg_window_id| be destroyed between when the capture
diff --git a/content/browser/media/capture/web_contents_frame_tracker.cc b/content/browser/media/capture/web_contents_frame_tracker.cc
index 24b9c01..e4ed168 100644
--- a/content/browser/media/capture/web_contents_frame_tracker.cc
+++ b/content/browser/media/capture/web_contents_frame_tracker.cc
@@ -212,14 +212,16 @@
 
   crop_id_ = crop_id;
 
-  const FrameSinkVideoCaptureDevice::VideoCaptureTarget target(
-      target_frame_sink_id_.value_or(viz::FrameSinkId()),
-      viz::SubtreeCaptureId(), crop_id_);
+  // If we don't have a target yet, we can store the crop ID but cannot actually
+  // crop yet.
+  if (!target_frame_sink_id_.is_valid())
+    return;
 
+  const viz::VideoCaptureTarget target(target_frame_sink_id_, crop_id_);
   device_task_runner_->PostTask(
       FROM_HERE,
       base::BindOnce(
-          [](const FrameSinkVideoCaptureDevice::VideoCaptureTarget& target,
+          [](const viz::VideoCaptureTarget& target,
              base::OnceCallback<void(media::mojom::CropRequestResult)> callback,
              base::WeakPtr<WebContentsVideoCaptureDevice> device) {
             if (!device) {
@@ -252,20 +254,26 @@
     return;
   }
 
-  // TODO(crbug.com/1247761): Clear |crop_id_| when share-this-tab-instead
-  // is clicked.
-
   viz::FrameSinkId frame_sink_id;
   if (context_) {
     frame_sink_id = context_->GetFrameSinkIdForCapture();
   }
+
+  // TODO(crbug.com/1247761): Clear |crop_id_| when share-this-tab-instead
+  // is clicked.
   if (frame_sink_id != target_frame_sink_id_) {
     target_frame_sink_id_ = frame_sink_id;
+    absl::optional<viz::VideoCaptureTarget> target;
+    if (frame_sink_id.is_valid()) {
+      target = viz::VideoCaptureTarget(frame_sink_id, crop_id_);
+    }
+
+    // The target may change to an invalid one, but we don't consider it
+    // permanently lost here yet.
     device_task_runner_->PostTask(
         FROM_HERE,
         base::BindOnce(&WebContentsVideoCaptureDevice::OnTargetChanged, device_,
-                       FrameSinkVideoCaptureDevice::VideoCaptureTarget(
-                           frame_sink_id, viz::SubtreeCaptureId(), crop_id_)));
+                       std::move(target)));
   }
 
   SetTargetView(web_contents()->GetNativeView());
diff --git a/content/browser/media/capture/web_contents_frame_tracker.h b/content/browser/media/capture/web_contents_frame_tracker.h
index 96c9e73..67a3884 100644
--- a/content/browser/media/capture/web_contents_frame_tracker.h
+++ b/content/browser/media/capture/web_contents_frame_tracker.h
@@ -129,7 +129,7 @@
 
   // We may not have a frame sink ID target at all times.
   std::unique_ptr<Context> context_;
-  absl::optional<viz::FrameSinkId> target_frame_sink_id_;
+  viz::FrameSinkId target_frame_sink_id_;
   base::Token crop_id_;
   gfx::NativeView target_native_view_ = gfx::NativeView();
 
diff --git a/content/browser/media/capture/web_contents_frame_tracker_unittest.cc b/content/browser/media/capture/web_contents_frame_tracker_unittest.cc
index d58b1bb..41a5fab0 100644
--- a/content/browser/media/capture/web_contents_frame_tracker_unittest.cc
+++ b/content/browser/media/capture/web_contents_frame_tracker_unittest.cc
@@ -74,7 +74,7 @@
  public:
   using WebContentsVideoCaptureDevice::AsWeakPtr;
   MOCK_METHOD1(OnTargetChanged,
-               void(const FrameSinkVideoCaptureDevice::VideoCaptureTarget&));
+               void(const absl::optional<viz::VideoCaptureTarget>&));
   MOCK_METHOD0(OnTargetPermanentlyLost, void());
 };
 
@@ -315,11 +315,12 @@
 // test the observer callbacks here.
 TEST_F(WebContentsFrameTrackerTest, NotifiesOfTargetChanges) {
   const viz::FrameSinkId kNewId(42, 1337);
-  EXPECT_CALL(*device(),
-              OnTargetChanged(FrameSinkVideoCaptureDevice::VideoCaptureTarget(
-                  kNewId, viz::SubtreeCaptureId(), /*crop_id=*/base::Token())))
-      .Times(1);
   SetFrameSinkId(kNewId);
+  EXPECT_CALL(
+      *device(),
+      OnTargetChanged(absl::make_optional<viz::VideoCaptureTarget>(kNewId)))
+      .Times(1);
+
   // The tracker doesn't actually use the frame host information, just
   // posts a possible target change.
   tracker()->RenderFrameHostChanged(nullptr, nullptr);
@@ -341,8 +342,8 @@
 
   // Expect OnTargetChanged() to be invoked once with the crop-ID.
   EXPECT_CALL(*device(),
-              OnTargetChanged(FrameSinkVideoCaptureDevice::VideoCaptureTarget(
-                  kInitSinkId, viz::SubtreeCaptureId(), kCropId)))
+              OnTargetChanged(absl::make_optional<viz::VideoCaptureTarget>(
+                  kInitSinkId, kCropId)))
       .Times(1);
 
   tracker()->Crop(kCropId, std::move(callback));
diff --git a/content/browser/renderer_host/render_widget_host_view_base.cc b/content/browser/renderer_host/render_widget_host_view_base.cc
index 8f29a8a..0399fd1 100644
--- a/content/browser/renderer_host/render_widget_host_view_base.cc
+++ b/content/browser/renderer_host/render_widget_host_view_base.cc
@@ -303,7 +303,7 @@
 RenderWidgetHostViewBase::CreateVideoCapturer() {
   std::unique_ptr<viz::ClientFrameSinkVideoCapturer> video_capturer =
       GetHostFrameSinkManager()->CreateVideoCapturer();
-  video_capturer->ChangeTarget(GetFrameSinkId(), nullptr);
+  video_capturer->ChangeTarget(viz::VideoCaptureTarget(GetFrameSinkId()));
   return video_capturer;
 }
 
diff --git a/services/viz/privileged/mojom/compositing/BUILD.gn b/services/viz/privileged/mojom/compositing/BUILD.gn
index 65d5f75..ee3bcd1 100644
--- a/services/viz/privileged/mojom/compositing/BUILD.gn
+++ b/services/viz/privileged/mojom/compositing/BUILD.gn
@@ -29,7 +29,12 @@
     "//ui/latency/mojom",
   ]
 
-  traits_public_deps = [ "//ui/base:features" ]
+  parser_deps = [ "//components/viz/common" ]
+
+  traits_public_deps = [
+    "//components/viz/common",
+    "//ui/base:features",
+  ]
 
   enabled_features = []
   if (use_ozone) {
diff --git a/services/viz/privileged/mojom/compositing/frame_sink_video_capture.mojom b/services/viz/privileged/mojom/compositing/frame_sink_video_capture.mojom
index 24945b1..38deefc 100644
--- a/services/viz/privileged/mojom/compositing/frame_sink_video_capture.mojom
+++ b/services/viz/privileged/mojom/compositing/frame_sink_video_capture.mojom
@@ -30,12 +30,17 @@
 };
 
 // Provided with FrameSinkVideoConsumer::ChangeTarget() to indicate what
-// sub target specifier should be used, if any.
-union SubTarget {
+// target should be selected for capture.
+union VideoCaptureSubTarget {
   SubtreeCaptureId subtree_capture_id;
   mojo_base.mojom.Token region_capture_crop_id;
 };
 
+struct VideoCaptureTarget {
+  FrameSinkId frame_sink_id;
+  VideoCaptureSubTarget? sub_target;
+};
+
 // Interface for a consumer that receives frames and notifications related to
 // capture of the source content. An instance that implements this interface is
 // provided to FrameSinkVideoCapturer.Start().
@@ -127,8 +132,9 @@
   SetAutoThrottlingEnabled(bool enabled);
 
   // Targets a different compositor frame sink. This may be called anytime,
-  // before or after Start(). If |frame_sink_id| is null, capture will suspend
-  // until a new frame sink target is set.
+  // before or after Start(). If |target| is null, capture will suspend
+  // until a new frame sink target is set. If |target| is provided, the
+  // frame sink identifier associated with it must be valid.
   // If the |sub_target| is a valid subtree capture id, the capturer will
   // capture a render pass associated with a layer subtree under the target
   // frame sink, which is identifiable by that
@@ -136,7 +142,7 @@
   // crop id, only the region associated with that crop id will be captured.
   // Otherwise, the capturer captures the root render pass of the target frame
   // sink.
-  ChangeTarget(FrameSinkId? frame_sink_id, SubTarget? sub_target);
+  ChangeTarget(VideoCaptureTarget? target);
 
   // Starts emitting video frames to the given |consumer|.
   Start(pending_remote<FrameSinkVideoConsumer> consumer);
@@ -150,7 +156,7 @@
   // resolve occasional "picture loss" issues consumer-side.
   RequestRefreshFrame();
 
-  // Creates an overlay to be renderered within each captured video frame. The
+  // Creates an overlay to be rendered within each captured video frame. The
   // |stacking_index| is an arbitrary value that determines whether to render
   // this overlay before/after other overlays. Greater values mean "after" and
   // "on top of" those with lesser values. Specifying the same index as an
diff --git a/services/viz/public/cpp/compositing/video_capture_target_mojom_traits.h b/services/viz/public/cpp/compositing/video_capture_target_mojom_traits.h
new file mode 100644
index 0000000..85e466de9
--- /dev/null
+++ b/services/viz/public/cpp/compositing/video_capture_target_mojom_traits.h
@@ -0,0 +1,104 @@
+// Copyright 2021 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef SERVICES_VIZ_PUBLIC_CPP_COMPOSITING_VIDEO_CAPTURE_TARGET_MOJOM_TRAITS_H_
+#define SERVICES_VIZ_PUBLIC_CPP_COMPOSITING_VIDEO_CAPTURE_TARGET_MOJOM_TRAITS_H_
+
+#include <utility>
+
+#include "build/build_config.h"
+#include "components/viz/common/surfaces/video_capture_target.h"
+#include "mojo/public/cpp/base/token_mojom_traits.h"
+#include "services/viz/privileged/mojom/compositing/frame_sink_video_capture.mojom-shared.h"
+#include "services/viz/public/cpp/compositing/frame_sink_id_mojom_traits.h"
+#include "services/viz/public/cpp/compositing/subtree_capture_id_mojom_traits.h"
+
+namespace mojo {
+
+template <>
+struct UnionTraits<viz::mojom::VideoCaptureSubTargetDataView,
+                   viz::VideoCaptureSubTarget> {
+  static bool IsNull(const viz::VideoCaptureSubTarget& data) {
+    return absl::holds_alternative<absl::monostate>(data);
+  }
+
+  static void SetToNull(viz::VideoCaptureSubTarget* data) {
+    *data = viz::VideoCaptureSubTarget();
+  }
+
+  static const viz::RegionCaptureCropId& region_capture_crop_id(
+      const viz::VideoCaptureSubTarget& data) {
+    return absl::get<viz::RegionCaptureCropId>(data);
+  }
+
+  static viz::SubtreeCaptureId subtree_capture_id(
+      const viz::VideoCaptureSubTarget& data) {
+    return absl::get<viz::SubtreeCaptureId>(data);
+  }
+
+  using Tag = viz::mojom::VideoCaptureSubTargetDataView::Tag;
+  static Tag GetTag(const viz::VideoCaptureSubTarget& data) {
+    if (absl::holds_alternative<viz::RegionCaptureCropId>(data)) {
+      return Tag::REGION_CAPTURE_CROP_ID;
+    }
+    DCHECK(absl::holds_alternative<viz::SubtreeCaptureId>(data));
+    return Tag::SUBTREE_CAPTURE_ID;
+  }
+
+  static bool Read(viz::mojom::VideoCaptureSubTargetDataView data,
+                   viz::VideoCaptureSubTarget* out) {
+    switch (data.tag()) {
+      case Tag::REGION_CAPTURE_CROP_ID: {
+        viz::RegionCaptureCropId crop_id;
+        if (!data.ReadRegionCaptureCropId(&crop_id) || crop_id.is_zero())
+          return false;
+
+        *out = crop_id;
+        return true;
+      }
+      case Tag::SUBTREE_CAPTURE_ID: {
+        viz::SubtreeCaptureId capture_id;
+        if (!data.ReadSubtreeCaptureId(&capture_id) || !capture_id.is_valid())
+          return false;
+
+        *out = capture_id;
+        return true;
+      }
+    }
+
+    NOTREACHED();
+    return false;
+  }
+};
+
+template <>
+struct StructTraits<viz::mojom::VideoCaptureTargetDataView,
+                    viz::VideoCaptureTarget> {
+  static viz::FrameSinkId frame_sink_id(const viz::VideoCaptureTarget& input) {
+    return input.frame_sink_id;
+  }
+
+  static const viz::VideoCaptureSubTarget& sub_target(
+      const viz::VideoCaptureTarget& input) {
+    return input.sub_target;
+  }
+
+  static bool Read(viz::mojom::VideoCaptureTargetDataView data,
+                   viz::VideoCaptureTarget* out) {
+    viz::VideoCaptureTarget target;
+    if (!data.ReadSubTarget(&target.sub_target) ||
+        !data.ReadFrameSinkId(&target.frame_sink_id))
+      return false;
+
+    if (!target.frame_sink_id.is_valid())
+      return false;
+
+    *out = std::move(target);
+    return true;
+  }
+};
+
+}  // namespace mojo
+
+#endif  // SERVICES_VIZ_PUBLIC_CPP_COMPOSITING_VIDEO_CAPTURE_TARGET_MOJOM_TRAITS_H_
diff --git a/services/viz/public/mojom/BUILD.gn b/services/viz/public/mojom/BUILD.gn
index 2f766bed..7411af8 100644
--- a/services/viz/public/mojom/BUILD.gn
+++ b/services/viz/public/mojom/BUILD.gn
@@ -183,6 +183,24 @@
       traits_headers = [ "//services/viz/public/cpp/compositing/vertical_scroll_direction_mojom_traits.h" ]
       traits_public_deps = [ "//components/viz/common" ]
     },
+    {
+      types = [
+        {
+          mojom = "viz.mojom.VideoCaptureTarget"
+          cpp = "::viz::VideoCaptureTarget"
+        },
+        {
+          mojom = "viz.mojom.VideoCaptureSubTarget"
+          cpp = "::viz::VideoCaptureSubTarget"
+          nullable_is_same_type = true
+        },
+      ]
+      traits_headers = [ "//services/viz/public/cpp/compositing/video_capture_target_mojom_traits.h" ]
+      traits_public_deps = [
+        "//components/viz/common",
+        "//ui/gfx/geometry/mojom",
+      ]
+    },
   ]
   cpp_typemaps = shared_cpp_typemaps
   cpp_typemaps += [