[go: nahoru, domu]

capture_mode: Add support for window capture

Implement the proposal presented in the design doc
http://go/window-capturing to make the
FrameSinkVideoCapturer able to record non-root windows.

tl;dr The idea is to tag the layer of the non-root window
with a unique ID (of type viz::SubtreeCaptureId) that will
result in forcing this sub layer tree into a render surface
that draw into a render pass identifiable by that ID to the
FrameSinkVideoCapturerImpl on the viz side.

BUG=1143930
TEST=- Manually.
     - Added new tests:
        - (viz_unittests) CompositorFrameSinkSupportTest.CopyRequestOnSubtree
        - (aura_unittests) WindowTest.*Capturable*
        - (ash_unittetss) CaptureModeTest.WindowRecordingCaptureId
        - (browser_tests) RecordingServiceBrowserTest.RecordWindow
        - (cc_unittests) *LayerTreeHostCaptureTest*
     - Modified many tests else where.

Change-Id: Iede1b923db391e6f390bb78a1d1aaf848ad7ab33
Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/2593724
Commit-Queue: Ahmed Fakhry <afakhry@chromium.org>
Reviewed-by: Scott Violet <sky@chromium.org>
Reviewed-by: kylechar <kylechar@chromium.org>
Reviewed-by: Daniel Cheng <dcheng@chromium.org>
Reviewed-by: danakj <danakj@chromium.org>
Reviewed-by: James Cook <jamescook@chromium.org>
Reviewed-by: Xianzhu Wang <wangxianzhu@chromium.org>
Cr-Commit-Position: refs/heads/master@{#840924}
diff --git a/android_webview/browser/gfx/hardware_renderer_single_thread.cc b/android_webview/browser/gfx/hardware_renderer_single_thread.cc
index 620320d..931765c2 100644
--- a/android_webview/browser/gfx/hardware_renderer_single_thread.cc
+++ b/android_webview/browser/gfx/hardware_renderer_single_thread.cc
@@ -119,7 +119,8 @@
   CopyOutputRequestQueue requests;
   requests.swap(child_frame_->copy_requests);
   for (auto& copy_request : requests) {
-    support_->RequestCopyOfOutput(child_id_, std::move(copy_request));
+    support_->RequestCopyOfOutput(
+        {child_id_, viz::SubtreeCaptureId(), std::move(copy_request)});
   }
 
   gfx::Rect clip(params.clip_left, params.clip_top,
diff --git a/ash/capture_mode/capture_mode_controller.cc b/ash/capture_mode/capture_mode_controller.cc
index dd637720..ea45c251 100644
--- a/ash/capture_mode/capture_mode_controller.cc
+++ b/ash/capture_mode/capture_mode_controller.cc
@@ -425,7 +425,6 @@
 
 void CaptureModeController::OnRecordingEnded(bool success) {
   delegate_->StopObservingRestrictedContent();
-  window_frame_sink_.reset();
 
   // If |success| is false, then recording has been force-terminated due to a
   // failure on the service side, or a disconnection to it. We need to terminate
@@ -579,12 +578,9 @@
         audio_stream_factory.InitWithNewPipeAndPassReceiver());
   }
 
-  auto frame_sink_id = capture_params.window->GetFrameSinkId();
-  if (!frame_sink_id.is_valid()) {
-    window_frame_sink_ = capture_params.window->CreateLayerTreeFrameSink();
-    frame_sink_id = capture_params.window->GetFrameSinkId();
-    DCHECK(frame_sink_id.is_valid());
-  }
+  const auto frame_sink_id =
+      capture_params.window->GetRootWindow()->GetFrameSinkId();
+
   const auto bounds = capture_params.bounds;
   switch (source_) {
     case CaptureModeSource::kFullscreen:
@@ -594,11 +590,18 @@
       break;
 
     case CaptureModeSource::kWindow:
-      // TODO(crbug.com/1143930): Window recording doesn't produce any frames at
-      // the moment.
+      // Non-root window are not capturable by the |FrameSinkVideoCapturer|
+      // unless its layer tree is identified by a |viz::SubtreeCaptureId|.
+      // The |VideoRecordingWatcher| that we create while recording is in
+      // progress creates a request to mark that window as capturable.
+      // See https://crbug.com/1143930 for more details.
+      DCHECK(!capture_params.window->IsRootWindow());
+      DCHECK(capture_params.window->subtree_capture_id().is_valid());
+
       recording_service_remote_->RecordWindow(
           std::move(client), std::move(video_capturer),
-          std::move(audio_stream_factory), frame_sink_id, bounds.size(),
+          std::move(audio_stream_factory), frame_sink_id,
+          capture_params.window->subtree_capture_id(), bounds.size(),
           capture_params.window->GetRootWindow()
               ->GetBoundsInRootWindow()
               .size());
diff --git a/ash/capture_mode/capture_mode_controller.h b/ash/capture_mode/capture_mode_controller.h
index 80f84c9..02b45bd1 100644
--- a/ash/capture_mode/capture_mode_controller.h
+++ b/ash/capture_mode/capture_mode_controller.h
@@ -33,10 +33,6 @@
 class SequencedTaskRunner;
 }  // namespace base
 
-namespace cc {
-class LayerTreeFrameSink;
-}  // namespace cc
-
 namespace ash {
 
 class CaptureModeSession;
@@ -283,12 +279,6 @@
   // If set, it will be called when either an image or video file is saved.
   base::OnceCallback<void(const base::FilePath&)> on_file_saved_callback_;
 
-  // Recording non-root windows require sending their FrameSinkIds to the
-  // recording service. Those windows won't have a valid ID unless we create
-  // LayerTreeFrameSinks for them. This remains alive while the window is being
-  // recorded.
-  std::unique_ptr<cc::LayerTreeFrameSink> window_frame_sink_;
-
   // Timers used to schedule recording of the number of screenshots taken.
   base::RepeatingTimer num_screenshots_taken_in_last_day_scheduler_;
   base::RepeatingTimer num_screenshots_taken_in_last_week_scheduler_;
diff --git a/ash/capture_mode/capture_mode_unittests.cc b/ash/capture_mode/capture_mode_unittests.cc
index b302b367..194f61e 100644
--- a/ash/capture_mode/capture_mode_unittests.cc
+++ b/ash/capture_mode/capture_mode_unittests.cc
@@ -1481,6 +1481,25 @@
       kTabletHistogram, CaptureModeEntryType::kAccelTakePartialScreenshot, 2);
 }
 
+TEST_F(CaptureModeTest, WindowRecordingCaptureId) {
+  auto window = CreateTestWindow(gfx::Rect(200, 200));
+  StartCaptureSession(CaptureModeSource::kWindow, CaptureModeType::kVideo);
+
+  auto* event_generator = GetEventGenerator();
+  event_generator->MoveMouseToCenterOf(window.get());
+  auto* controller = CaptureModeController::Get();
+  controller->StartVideoRecordingImmediatelyForTesting();
+  EXPECT_TRUE(controller->is_recording_in_progress());
+
+  // The window should have a valid capture ID.
+  EXPECT_TRUE(window->subtree_capture_id().is_valid());
+
+  // Once recording ends, the window should no longer be marked as capturable.
+  controller->EndVideoRecording(EndRecordingReason::kStopRecordingButton);
+  EXPECT_FALSE(controller->is_recording_in_progress());
+  EXPECT_FALSE(window->subtree_capture_id().is_valid());
+}
+
 TEST_F(CaptureModeTest, ClosingWindowBeingRecorded) {
   auto window = CreateTestWindow(gfx::Rect(200, 200));
   StartCaptureSession(CaptureModeSource::kWindow, CaptureModeType::kVideo);
@@ -1491,6 +1510,9 @@
   controller->StartVideoRecordingImmediatelyForTesting();
   EXPECT_TRUE(controller->is_recording_in_progress());
 
+  // The window should have a valid capture ID.
+  EXPECT_TRUE(window->subtree_capture_id().is_valid());
+
   // Closing the window being recorded should end video recording.
   base::HistogramTester histogram_tester;
   window.reset();
@@ -1939,6 +1961,7 @@
 // mode when capturing a window. Regression test for https://crbug.com/1152938.
 TEST_F(CaptureModeTest, TabletTouchCaptureLabelWidgetWindowMode) {
   TabletModeControllerTestApi tablet_mode_controller_test_api;
+  tablet_mode_controller_test_api.DetachAllMice();
   tablet_mode_controller_test_api.EnterTabletMode();
 
   // Enter capture window mode.
diff --git a/ash/capture_mode/test_capture_mode_delegate.cc b/ash/capture_mode/test_capture_mode_delegate.cc
index 22fed2e..4d606e5 100644
--- a/ash/capture_mode/test_capture_mode_delegate.cc
+++ b/ash/capture_mode/test_capture_mode_delegate.cc
@@ -41,6 +41,7 @@
       mojo::PendingRemote<viz::mojom::FrameSinkVideoCapturer> video_capturer,
       mojo::PendingRemote<audio::mojom::StreamFactory> audio_stream_factory,
       const viz::FrameSinkId& frame_sink_id,
+      const viz::SubtreeCaptureId& subtree_capture_id,
       const gfx::Size& initial_window_size,
       const gfx::Size& max_window_size) override {
     remote_client_.Bind(std::move(client));
diff --git a/ash/capture_mode/video_recording_watcher.cc b/ash/capture_mode/video_recording_watcher.cc
index af56308f..c5d55bd 100644
--- a/ash/capture_mode/video_recording_watcher.cc
+++ b/ash/capture_mode/video_recording_watcher.cc
@@ -22,6 +22,11 @@
   DCHECK(window_being_recorded_);
   DCHECK(controller_->is_recording_in_progress());
 
+  if (!window_being_recorded_->IsRootWindow()) {
+    non_root_window_capture_request_ =
+        window_being_recorded_->MakeWindowCapturable();
+  }
+
   window_being_recorded_->AddObserver(this);
 }
 
diff --git a/ash/capture_mode/video_recording_watcher.h b/ash/capture_mode/video_recording_watcher.h
index 0130d6b..792d771 100644
--- a/ash/capture_mode/video_recording_watcher.h
+++ b/ash/capture_mode/video_recording_watcher.h
@@ -5,6 +5,7 @@
 #ifndef ASH_CAPTURE_MODE_VIDEO_RECORDING_WATCHER_H_
 #define ASH_CAPTURE_MODE_VIDEO_RECORDING_WATCHER_H_
 
+#include "ui/aura/scoped_window_capture_request.h"
 #include "ui/aura/window_observer.h"
 
 namespace ash {
@@ -34,6 +35,10 @@
  private:
   CaptureModeController* const controller_;
   aura::Window* const window_being_recorded_;
+
+  // If |window_being_recorded_| is not a root window, we must make a request to
+  // make it capturable by the |FrameSinkVideoCapturer|.
+  aura::ScopedWindowCaptureRequest non_root_window_capture_request_;
 };
 
 }  // namespace ash
diff --git a/ash/services/recording/public/mojom/recording_service.mojom b/ash/services/recording/public/mojom/recording_service.mojom
index f53cbea..8ec490d 100644
--- a/ash/services/recording/public/mojom/recording_service.mojom
+++ b/ash/services/recording/public/mojom/recording_service.mojom
@@ -8,6 +8,7 @@
 import "services/audio/public/mojom/stream_factory.mojom";
 import "services/viz/privileged/mojom/compositing/frame_sink_video_capture.mojom";
 import "services/viz/public/mojom/compositing/frame_sink_id.mojom";
+import "services/viz/public/mojom/compositing/subtree_capture_id.mojom";
 import "ui/gfx/geometry/mojom/geometry.mojom";
 
 // Defines the interface for the recording service client (e.g. ash), which
@@ -30,7 +31,7 @@
 
 // Defines the interface of the recording service which is implemented by
 // |recording::RecordingService| and runs in its own utility process. It is
-// launched by the process on which the |RecordingServiceClient| resides, and is
+// launched by the Ash on which the |RecordingServiceClient| resides, and is
 // used to perform audio/video recording of the whole screen, a partial region
 // of it, or an individual window. The service captures, encodes, and muxes the
 // audio and video frames, and sends the WebM muxed video chunks to the client.
@@ -55,7 +56,14 @@
       viz.mojom.FrameSinkId frame_sink_id,
       gfx.mojom.Size video_size);
 
-  // Starts a recording of a window which has the given |frame_sink_id|.
+  // Starts a recording of a window. If this window has a valid |frame_sink_id|,
+  // and submits its own compositor frames independently, then
+  // |subtree_capture_id| can be default-constructed and won't be used.
+  // Otherwise, for windows that don't submit compositor frames (e.g. non-root
+  // windows), the given |frame_sink_id| must be of the root window they're
+  // descendant from, and they must be made capturable by tagging the with a
+  // valid |subtree_capture_id| before calling this (see
+  // aura::Window::MakeWindowCapturable()).
   // |initial_video_size| and |max_video_size| specify a range of acceptable
   // capture resolutions in DIPs. The resolution of the output will adapt
   // dynamically as the window being recorded gets resized by the end user (e.g.
@@ -68,6 +76,7 @@
       pending_remote<viz.mojom.FrameSinkVideoCapturer> video_capturer,
       pending_remote<audio.mojom.StreamFactory>? audio_stream_factory,
       viz.mojom.FrameSinkId frame_sink_id,
+      viz.mojom.SubtreeCaptureId subtree_capture_id,
       gfx.mojom.Size initial_video_size,
       gfx.mojom.Size max_video_size);
 
diff --git a/ash/services/recording/recording_service.cc b/ash/services/recording/recording_service.cc
index 907d5e7ce..62082cd 100644
--- a/ash/services/recording/recording_service.cc
+++ b/ash/services/recording/recording_service.cc
@@ -96,6 +96,7 @@
     mojo::PendingRemote<viz::mojom::FrameSinkVideoCapturer> video_capturer,
     mojo::PendingRemote<audio::mojom::StreamFactory> audio_stream_factory,
     const viz::FrameSinkId& frame_sink_id,
+    const viz::SubtreeCaptureId& subtree_capture_id,
     const gfx::Size& initial_video_size,
     const gfx::Size& max_video_size) {
   DCHECK_CALLED_ON_VALID_THREAD(main_thread_checker_);
@@ -105,7 +106,8 @@
   StartNewRecording(std::move(client), std::move(video_capturer),
                     std::move(audio_stream_factory),
                     VideoCaptureParams::CreateForWindowCapture(
-                        frame_sink_id, initial_video_size, max_video_size));
+                        frame_sink_id, subtree_capture_id, initial_video_size,
+                        max_video_size));
 }
 
 void RecordingService::RecordRegion(
diff --git a/ash/services/recording/recording_service.h b/ash/services/recording/recording_service.h
index 79f8dc6..d529b19 100644
--- a/ash/services/recording/recording_service.h
+++ b/ash/services/recording/recording_service.h
@@ -53,6 +53,7 @@
       mojo::PendingRemote<viz::mojom::FrameSinkVideoCapturer> video_capturer,
       mojo::PendingRemote<audio::mojom::StreamFactory> audio_stream_factory,
       const viz::FrameSinkId& frame_sink_id,
+      const viz::SubtreeCaptureId& subtree_capture_id,
       const gfx::Size& initial_video_size,
       const gfx::Size& max_video_size) override;
   void RecordRegion(
diff --git a/ash/services/recording/video_capture_params.cc b/ash/services/recording/video_capture_params.cc
index e3c52d6..cb9ba0b 100644
--- a/ash/services/recording/video_capture_params.cc
+++ b/ash/services/recording/video_capture_params.cc
@@ -7,6 +7,7 @@
 #include "ash/services/recording/recording_service_constants.h"
 #include "base/check.h"
 #include "base/time/time.h"
+#include "components/viz/common/surfaces/subtree_capture_id.h"
 #include "media/base/video_types.h"
 #include "services/viz/privileged/mojom/compositing/frame_sink_video_capture.mojom.h"
 #include "ui/gfx/geometry/rect.h"
@@ -23,7 +24,8 @@
  public:
   FullscreenCaptureParams(viz::FrameSinkId frame_sink_id,
                           const gfx::Size& video_size)
-      : VideoCaptureParams(frame_sink_id), video_size_(video_size) {}
+      : VideoCaptureParams(frame_sink_id, viz::SubtreeCaptureId()),
+        video_size_(video_size) {}
   FullscreenCaptureParams(const FullscreenCaptureParams&) = delete;
   FullscreenCaptureParams& operator=(const FullscreenCaptureParams&) = delete;
   ~FullscreenCaptureParams() override = default;
@@ -49,9 +51,10 @@
 class WindowCaptureParams : public VideoCaptureParams {
  public:
   WindowCaptureParams(viz::FrameSinkId frame_sink_id,
+                      viz::SubtreeCaptureId subtree_capture_id,
                       const gfx::Size& initial_video_size,
                       const gfx::Size& max_video_size)
-      : VideoCaptureParams(frame_sink_id),
+      : VideoCaptureParams(frame_sink_id, subtree_capture_id),
         initial_video_size_(initial_video_size),
         max_video_size_(max_video_size) {}
   WindowCaptureParams(const WindowCaptureParams&) = delete;
@@ -82,7 +85,7 @@
   RegionCaptureParams(viz::FrameSinkId frame_sink_id,
                       const gfx::Size& full_capture_size,
                       const gfx::Rect& crop_region)
-      : VideoCaptureParams(frame_sink_id),
+      : VideoCaptureParams(frame_sink_id, viz::SubtreeCaptureId()),
         full_capture_size_(full_capture_size),
         crop_region_(crop_region) {}
   RegionCaptureParams(const RegionCaptureParams&) = delete;
@@ -125,10 +128,11 @@
 // static
 std::unique_ptr<VideoCaptureParams> VideoCaptureParams::CreateForWindowCapture(
     viz::FrameSinkId frame_sink_id,
+    viz::SubtreeCaptureId subtree_capture_id,
     const gfx::Size& initial_video_size,
     const gfx::Size& max_video_size) {
   return std::make_unique<WindowCaptureParams>(
-      frame_sink_id, initial_video_size, max_video_size);
+      frame_sink_id, subtree_capture_id, initial_video_size, max_video_size);
 }
 
 // static
@@ -149,7 +153,7 @@
   // TODO(afakhry): Discuss with //media/ team the implications of color space
   // conversions.
   capturer->SetFormat(media::PIXEL_FORMAT_I420, kColorSpace);
-  capturer->ChangeTarget(frame_sink_id_);
+  capturer->ChangeTarget(frame_sink_id_, subtree_capture_id_);
 }
 
 gfx::Rect VideoCaptureParams::GetVideoFrameVisibleRect(
@@ -157,8 +161,9 @@
   return original_frame_visible_rect;
 }
 
-VideoCaptureParams::VideoCaptureParams(viz::FrameSinkId frame_sink_id)
-    : frame_sink_id_(frame_sink_id) {
+VideoCaptureParams::VideoCaptureParams(viz::FrameSinkId frame_sink_id,
+                                       viz::SubtreeCaptureId subtree_capture_id)
+    : frame_sink_id_(frame_sink_id), subtree_capture_id_(subtree_capture_id) {
   DCHECK(frame_sink_id_.is_valid());
 }
 
diff --git a/ash/services/recording/video_capture_params.h b/ash/services/recording/video_capture_params.h
index 0559b61..3726f345 100644
--- a/ash/services/recording/video_capture_params.h
+++ b/ash/services/recording/video_capture_params.h
@@ -8,6 +8,7 @@
 #include <memory>
 
 #include "components/viz/common/surfaces/frame_sink_id.h"
+#include "components/viz/common/surfaces/subtree_capture_id.h"
 #include "mojo/public/cpp/bindings/remote.h"
 #include "services/viz/privileged/mojom/compositing/frame_sink_video_capture.mojom-forward.h"
 
@@ -35,14 +36,19 @@
       viz::FrameSinkId frame_sink_id,
       const gfx::Size& video_size);
 
-  // Returns a capture params instance for a recording of a window which has
-  // the given |frame_sink_id| and its initial size is given as
+  // Returns a capture params instance for a recording of a window. The given
+  //|frame_sink_id| is either of that window (if it submits compositor frames
+  // independently), or of the root window it descends from (if it doesn't
+  // submit its compositor frames). In the latter case, the window must be
+  // identifiable by a valid |subtree_capture_id| (created by calling
+  // aura::window::MakeWindowCapturable() before recording starts).
   // |initial_video_size| and |max_video_size| specify a range of acceptable
   // capture resolutions in DIPs. The resolution of the output will adapt
   // dynamically as the window being recorded gets resized by the end user (e.g.
   // resized, maximized, fullscreened, ... etc.). |frame_sink_id| must be valid.
   static std::unique_ptr<VideoCaptureParams> CreateForWindowCapture(
       viz::FrameSinkId frame_sink_id,
+      viz::SubtreeCaptureId subtree_capture_id,
       const gfx::Size& initial_video_size,
       const gfx::Size& max_video_size);
 
@@ -73,9 +79,11 @@
   virtual gfx::Size GetCaptureSize() const = 0;
 
  protected:
-  explicit VideoCaptureParams(viz::FrameSinkId frame_sink_id);
+  explicit VideoCaptureParams(viz::FrameSinkId frame_sink_id,
+                              viz::SubtreeCaptureId subtree_capture_id);
 
   const viz::FrameSinkId frame_sink_id_;
+  const viz::SubtreeCaptureId subtree_capture_id_;
 };
 
 }  // namespace recording
diff --git a/cc/BUILD.gn b/cc/BUILD.gn
index c58dc050..8c8f34b 100644
--- a/cc/BUILD.gn
+++ b/cc/BUILD.gn
@@ -767,6 +767,7 @@
     "trees/layer_tree_host_pixeltest_tiles.cc",
     "trees/layer_tree_host_unittest.cc",
     "trees/layer_tree_host_unittest_animation.cc",
+    "trees/layer_tree_host_unittest_capture.cc",
     "trees/layer_tree_host_unittest_capture_content.cc",
     "trees/layer_tree_host_unittest_checkerimaging.cc",
     "trees/layer_tree_host_unittest_context.cc",
diff --git a/cc/layers/layer.cc b/cc/layers/layer.cc
index 3f187ae..2a0de3af 100644
--- a/cc/layers/layer.cc
+++ b/cc/layers/layer.cc
@@ -1003,6 +1003,22 @@
   EnsureLayerTreeInputs().did_scroll_callback = std::move(callback);
 }
 
+void Layer::SetSubtreeCaptureId(viz::SubtreeCaptureId subtree_id) {
+  DCHECK(IsPropertyChangeAllowed());
+
+  auto& inputs = EnsureLayerTreeInputs();
+  if (inputs.subtree_capture_id == subtree_id)
+    return;
+
+  DCHECK(!inputs.subtree_capture_id.is_valid() || !subtree_id.is_valid())
+      << "Not allowed to change from a valid ID to another valid ID, as it may "
+         "already be in use.";
+
+  inputs.subtree_capture_id = subtree_id;
+  SetPropertyTreesNeedRebuild();
+  SetNeedsCommit();
+}
+
 void Layer::SetScrollable(const gfx::Size& bounds) {
   DCHECK(IsPropertyChangeAllowed());
   auto& inputs = EnsureLayerTreeInputs();
diff --git a/cc/layers/layer.h b/cc/layers/layer.h
index 3f4189e..f73be66 100644
--- a/cc/layers/layer.h
+++ b/cc/layers/layer.h
@@ -31,6 +31,7 @@
 #include "cc/trees/effect_node.h"
 #include "cc/trees/property_tree.h"
 #include "cc/trees/target_property.h"
+#include "components/viz/common/surfaces/subtree_capture_id.h"
 #include "third_party/skia/include/core/SkColor.h"
 #include "ui/gfx/geometry/point3_f.h"
 #include "ui/gfx/geometry/rect.h"
@@ -472,6 +473,27 @@
   void SetDidScrollCallback(base::RepeatingCallback<
                             void(const gfx::ScrollOffset&, const ElementId&)>);
 
+  // For layer tree mode only.
+  // Sets the given |subtree_id| on this layer, so that the layer subtree rooted
+  // at this layer can be uniquely identified by a FrameSinkVideoCapturer.
+  // The existence of a valid SubtreeCaptureId on this layer will force it to be
+  // drawn into a separate CompositorRenderPass.
+  // Setting a non-valid (i.e. default-constructed SubtreeCaptureId) will clear
+  // this property.
+  // It is not allowed to change this ID from a valid ID to another valid ID,
+  // since a client might already using the existing valid ID to make this layer
+  // subtree identifiable by a capturer.
+  //
+  // Note that this is useful when it's desired to video record a layer subtree
+  // of a non-root layer using a FrameSinkVideoCapturer, since non-root layers
+  // are usually not drawn into their own CompositorRenderPass.
+  void SetSubtreeCaptureId(viz::SubtreeCaptureId subtree_id);
+  viz::SubtreeCaptureId subtree_capture_id() const {
+    if (layer_tree_inputs())
+      return layer_tree_inputs()->subtree_capture_id;
+    return viz::SubtreeCaptureId();
+  }
+
   // Set or get if the layer and its subtree should be cached as a texture in
   // the display compositor. This is used as an optimization when it is known
   // that the layer will be animated without changing its content, or any of its
@@ -889,6 +911,12 @@
     gfx::Transform transform;
     gfx::Point3F transform_origin;
 
+    // A unique ID that identifies the layer subtree rooted at this layer, so
+    // that it can be independently captured by the FrameSinkVideoCapturer. If
+    // this ID is set (i.e. valid), it would force this subtree into a render
+    // surface that darws in a render pass.
+    viz::SubtreeCaptureId subtree_capture_id;
+
     SkColor safe_opaque_background_color = SK_ColorTRANSPARENT;
 
     FilterOperations filters;
@@ -927,6 +955,7 @@
   int clip_tree_index_;
   int scroll_tree_index_;
   int property_tree_sequence_number_;
+
   gfx::Vector2dF offset_to_transform_parent_;
 
   // When true, the layer is about to perform an update. Any commit requests
diff --git a/cc/layers/render_surface_impl.cc b/cc/layers/render_surface_impl.cc
index ca8fa5a7..98b2d611 100644
--- a/cc/layers/render_surface_impl.cc
+++ b/cc/layers/render_surface_impl.cc
@@ -157,10 +157,19 @@
   return OwningEffectNode()->has_copy_request;
 }
 
+viz::SubtreeCaptureId RenderSurfaceImpl::SubtreeCaptureId() const {
+  return OwningEffectNode()->subtree_capture_id;
+}
+
 bool RenderSurfaceImpl::ShouldCacheRenderSurface() const {
   return OwningEffectNode()->cache_render_surface;
 }
 
+bool RenderSurfaceImpl::CopyOfOutputRequired() const {
+  return HasCopyRequest() || ShouldCacheRenderSurface() ||
+         SubtreeCaptureId().is_valid();
+}
+
 int RenderSurfaceImpl::TransformTreeIndex() const {
   return OwningEffectNode()->transform_id;
 }
@@ -210,7 +219,7 @@
 }
 
 gfx::Rect RenderSurfaceImpl::CalculateClippedAccumulatedContentRect() {
-  if (ShouldCacheRenderSurface() || HasCopyRequest() || !is_clipped())
+  if (CopyOfOutputRequired() || !is_clipped())
     return accumulated_content_rect();
 
   if (accumulated_content_rect().IsEmpty())
@@ -377,6 +386,7 @@
   pass->backdrop_filters = BackdropFilters();
   pass->backdrop_filter_bounds = BackdropFilterBounds();
   pass->generate_mipmap = TrilinearFiltering();
+  pass->subtree_capture_id = SubtreeCaptureId();
   pass->cache_render_pass = ShouldCacheRenderSurface();
   pass->has_damage_from_contributing_content =
       HasDamageFromeContributingContent();
diff --git a/cc/layers/render_surface_impl.h b/cc/layers/render_surface_impl.h
index 51b07a9..105e04a 100644
--- a/cc/layers/render_surface_impl.h
+++ b/cc/layers/render_surface_impl.h
@@ -18,6 +18,7 @@
 #include "cc/trees/property_tree.h"
 #include "components/viz/common/quads/compositor_render_pass.h"
 #include "components/viz/common/quads/shared_quad_state.h"
+#include "components/viz/common/surfaces/subtree_capture_id.h"
 #include "ui/gfx/geometry/rect.h"
 #include "ui/gfx/geometry/rect_f.h"
 #include "ui/gfx/mask_filter_info.h"
@@ -174,8 +175,15 @@
 
   bool HasCopyRequest() const;
 
+  viz::SubtreeCaptureId SubtreeCaptureId() const;
+
   bool ShouldCacheRenderSurface() const;
 
+  // Returns true if it's required to copy the output of this surface (i.e. when
+  // it has copy requests, should be cached, or has a valid subtree capture ID),
+  // and should be e.g. immune from occlusion, etc. Returns false otherise.
+  bool CopyOfOutputRequired() const;
+
   void ResetPropertyChangedFlags();
   bool SurfacePropertyChanged() const;
   bool SurfacePropertyChangedOnlyFromDescendant() const;
diff --git a/cc/trees/draw_property_utils.cc b/cc/trees/draw_property_utils.cc
index c2e79fc..aa4433e 100644
--- a/cc/trees/draw_property_utils.cc
+++ b/cc/trees/draw_property_utils.cc
@@ -640,19 +640,20 @@
 gfx::Rect LayerVisibleRect(PropertyTrees* property_trees, LayerImpl* layer) {
   const EffectNode* effect_node =
       property_trees->effect_tree.Node(layer->effect_tree_index());
-  int effect_ancestor_with_cache_render_surface =
-      effect_node->closest_ancestor_with_cached_render_surface_id;
-  int effect_ancestor_with_copy_request =
-      effect_node->closest_ancestor_with_copy_request_id;
   int lower_effect_closest_ancestor =
-      std::max(effect_ancestor_with_cache_render_surface,
-               effect_ancestor_with_copy_request);
-  bool non_root_copy_request_or_cache_render_surface =
+      effect_node->closest_ancestor_with_cached_render_surface_id;
+  lower_effect_closest_ancestor =
+      std::max(lower_effect_closest_ancestor,
+               effect_node->closest_ancestor_with_copy_request_id);
+  lower_effect_closest_ancestor =
+      std::max(lower_effect_closest_ancestor,
+               effect_node->closest_ancestor_being_captured_id);
+  const bool non_root_with_render_surface =
       lower_effect_closest_ancestor > EffectTree::kContentsRootNodeId;
   gfx::Rect layer_content_rect = gfx::Rect(layer->bounds());
 
   gfx::RectF accumulated_clip_in_root_space;
-  if (non_root_copy_request_or_cache_render_surface) {
+  if (non_root_with_render_surface) {
     bool include_expanding_clips = true;
     ConditionalClip accumulated_clip = ComputeAccumulatedClip(
         property_trees, include_expanding_clips, layer->clip_tree_index(),
@@ -668,7 +669,7 @@
   }
 
   const EffectNode* root_effect_node =
-      non_root_copy_request_or_cache_render_surface
+      non_root_with_render_surface
           ? property_trees->effect_tree.Node(lower_effect_closest_ancestor)
           : property_trees->effect_tree.Node(EffectTree::kContentsRootNodeId);
   ConditionalClip accumulated_clip_in_layer_space =
@@ -890,8 +891,7 @@
   // TODO(senorblanco): make this smarter for the SkImageFilter case (check for
   // pixel-moving filters)
   const FilterOperations& filters = render_surface->Filters();
-  bool is_occlusion_immune = render_surface->HasCopyRequest() ||
-                             render_surface->ShouldCacheRenderSurface() ||
+  bool is_occlusion_immune = render_surface->CopyOfOutputRequired() ||
                              filters.HasReferenceFilter() ||
                              filters.HasFilterThatMovesPixels();
   if (is_occlusion_immune) {
@@ -1225,9 +1225,11 @@
   if (effect_node->HasRenderSurface() && effect_node->subtree_has_copy_request)
     return false;
 
-  // Skip if the node's subtree is hidden and no need to cache.
-  if (effect_node->subtree_hidden && !effect_node->cache_render_surface)
+  // Skip if the node's subtree is hidden and no need to cache, or capture.
+  if (effect_node->subtree_hidden && !effect_node->cache_render_surface &&
+      !effect_node->subtree_capture_id.is_valid()) {
     return true;
+  }
 
   // If the layer transform is not invertible, it should be skipped. In case the
   // transform is animating and singular, we should not skip it.
diff --git a/cc/trees/effect_node.cc b/cc/trees/effect_node.cc
index fa07fea5..413f5888 100644
--- a/cc/trees/effect_node.cc
+++ b/cc/trees/effect_node.cc
@@ -42,7 +42,8 @@
       clip_id(0),
       target_id(1),
       closest_ancestor_with_cached_render_surface_id(-1),
-      closest_ancestor_with_copy_request_id(-1) {}
+      closest_ancestor_with_copy_request_id(-1),
+      closest_ancestor_being_captured_id(-1) {}
 
 EffectNode::EffectNode(const EffectNode& other) = default;
 
@@ -54,6 +55,7 @@
          stable_id == other.stable_id && opacity == other.opacity &&
          screen_space_opacity == other.screen_space_opacity &&
          backdrop_filter_quality == other.backdrop_filter_quality &&
+         subtree_capture_id == other.subtree_capture_id &&
          cache_render_surface == other.cache_render_surface &&
          has_copy_request == other.has_copy_request &&
          filters == other.filters &&
@@ -94,7 +96,9 @@
          closest_ancestor_with_cached_render_surface_id ==
              other.closest_ancestor_with_cached_render_surface_id &&
          closest_ancestor_with_copy_request_id ==
-             other.closest_ancestor_with_copy_request_id;
+             other.closest_ancestor_with_copy_request_id &&
+         closest_ancestor_being_captured_id ==
+             other.closest_ancestor_being_captured_id;
 }
 #endif  // DCHECK_IS_ON()
 
@@ -172,6 +176,7 @@
     }
   }
   value->SetString("blend_mode", SkBlendMode_Name(blend_mode));
+  value->SetString("subtree_capture_id", subtree_capture_id.ToString());
   value->SetBoolean("cache_render_surface", cache_render_surface);
   value->SetBoolean("has_copy_request", has_copy_request);
   value->SetBoolean("double_sided", double_sided);
@@ -198,6 +203,8 @@
                     closest_ancestor_with_cached_render_surface_id);
   value->SetInteger("closest_ancestor_with_copy_request_id",
                     closest_ancestor_with_copy_request_id);
+  value->SetInteger("closest_ancestor_being_captured_id",
+                    closest_ancestor_being_captured_id);
   value->SetBoolean("affected_by_backdrop_filter", affected_by_backdrop_filter);
 }
 
diff --git a/cc/trees/effect_node.h b/cc/trees/effect_node.h
index 2a2541d1..65edbca 100644
--- a/cc/trees/effect_node.h
+++ b/cc/trees/effect_node.h
@@ -8,6 +8,7 @@
 #include "cc/cc_export.h"
 #include "cc/paint/element_id.h"
 #include "cc/paint/filter_operations.h"
+#include "components/viz/common/surfaces/subtree_capture_id.h"
 #include "third_party/skia/include/core/SkBlendMode.h"
 #include "ui/gfx/geometry/point_f.h"
 #include "ui/gfx/geometry/size_f.h"
@@ -44,6 +45,7 @@
   kCache,
   kCopyRequest,
   kMirrored,
+  kSubtreeIsBeingCaptured,
   // This must be the last value because it's used in tracing code to know the
   // number of reasons.
   kTest,
@@ -90,6 +92,8 @@
 
   gfx::Vector2dF surface_contents_scale;
 
+  viz::SubtreeCaptureId subtree_capture_id;
+
   bool cache_render_surface : 1;
   bool has_copy_request : 1;
   bool hidden_by_backface_visibility : 1;
@@ -154,6 +158,7 @@
   int target_id;
   int closest_ancestor_with_cached_render_surface_id;
   int closest_ancestor_with_copy_request_id;
+  int closest_ancestor_being_captured_id;
 
   bool HasRenderSurface() const {
     return render_surface_reason != RenderSurfaceReason::kNone;
diff --git a/cc/trees/layer_tree_host_impl.cc b/cc/trees/layer_tree_host_impl.cc
index 55c7a74..7b11dac 100644
--- a/cc/trees/layer_tree_host_impl.cc
+++ b/cc/trees/layer_tree_host_impl.cc
@@ -1170,8 +1170,7 @@
         render_surface->EffectTreeIndex() == EffectTree::kContentsRootNodeId;
     bool should_draw_into_render_pass =
         is_root_surface || render_surface->contributes_to_drawn_surface() ||
-        render_surface->HasCopyRequest() ||
-        render_surface->ShouldCacheRenderSurface();
+        render_surface->CopyOfOutputRequired();
     if (should_draw_into_render_pass)
       frame->render_passes.push_back(render_surface->CreateRenderPass());
   }
@@ -1598,7 +1597,8 @@
     }
 
     if (pass->quad_list.empty() && pass->copy_requests.empty() &&
-        pass->filters.IsEmpty() && pass->backdrop_filters.IsEmpty()) {
+        !pass->subtree_capture_id.is_valid() && pass->filters.IsEmpty() &&
+        pass->backdrop_filters.IsEmpty()) {
       // Remove the pass and decrement |i| to counter the for loop's increment,
       // so we don't skip the next pass in the loop.
       frame->render_passes.erase(frame->render_passes.begin() + i);
diff --git a/cc/trees/layer_tree_host_unittest_capture.cc b/cc/trees/layer_tree_host_unittest_capture.cc
new file mode 100644
index 0000000..0336419
--- /dev/null
+++ b/cc/trees/layer_tree_host_unittest_capture.cc
@@ -0,0 +1,143 @@
+// Copyright 2020 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 "cc/test/fake_content_layer_client.h"
+#include "cc/test/fake_picture_layer.h"
+#include "cc/test/layer_tree_test.h"
+#include "cc/test/property_tree_test_utils.h"
+#include "cc/trees/layer_tree_impl.h"
+#include "components/viz/common/quads/compositor_frame.h"
+#include "components/viz/common/surfaces/subtree_capture_id.h"
+
+namespace cc {
+namespace {
+
+constexpr viz::SubtreeCaptureId kCaptureId{22};
+
+// A base class for tests that verifies the bahvior of the layer tree when a
+// sub layer has a valid viz::SubtreeCaptureId.
+class LayerTreeHostCaptureTest : public LayerTreeTest {
+ public:
+  void SetupTree() override {
+    scoped_refptr<Layer> root = FakePictureLayer::Create(&client_);
+    root->SetBounds(gfx::Size(100, 100));
+
+    child_ = FakePictureLayer::Create(&client_);
+    child_->SetBounds(gfx::Size(50, 60));
+    child_->SetPosition(gfx::PointF(10.f, 5.5f));
+    root->AddChild(child_);
+
+    grand_child_ = FakePictureLayer::Create(&client_);
+    grand_child_->SetBounds(gfx::Size(70, 30));
+    grand_child_->SetPosition(gfx::PointF(50.f, 50.f));
+    child_->AddChild(grand_child_);
+
+    layer_tree_host()->SetRootLayer(root);
+    LayerTreeTest::SetupTree();
+    client_.set_bounds(root->bounds());
+  }
+
+  void DrawLayersOnThread(LayerTreeHostImpl* impl) override {
+    LayerImpl* root = impl->active_tree()->root_layer();
+    LayerImpl* child = impl->active_tree()->LayerById(child_->id());
+    LayerImpl* grand_child = impl->active_tree()->LayerById(grand_child_->id());
+
+    VerifyLayerImpls(root, child, grand_child);
+  }
+
+ protected:
+  // Lets test subclasses to verify the LayerImpls of the layers in the tree.
+  virtual void VerifyLayerImpls(LayerImpl* root,
+                                LayerImpl* child,
+                                LayerImpl* grand_child) = 0;
+
+  FakeContentLayerClient client_;
+  scoped_refptr<Layer> child_;
+  scoped_refptr<Layer> grand_child_;
+};
+
+// -----------------------------------------------------------------------------
+// LayerTreeHostCaptureTestNoExtraRenderPassWhenNotCapturing:
+//
+// Tests that a layer tree that doesn't have a viz::SubtreeCaptureId on any of
+// its layers, draw in a single root render surface, and generates a single
+// compositor render pass.
+class LayerTreeHostCaptureTestNoExtraRenderPassWhenNotCapturing
+    : public LayerTreeHostCaptureTest {
+ public:
+  void BeginTest() override { PostSetNeedsCommitToMainThread(); }
+
+  void VerifyLayerImpls(LayerImpl* root,
+                        LayerImpl* child,
+                        LayerImpl* grand_child) override {
+    // All layers in the tree draw in the same root render surface.
+    auto* root_surface = GetRenderSurface(root);
+    auto* child_surface = GetRenderSurface(child);
+    auto* grand_child_surface = GetRenderSurface(grand_child);
+    EXPECT_EQ(root_surface, child_surface);
+    EXPECT_EQ(root_surface, grand_child_surface);
+    EXPECT_FALSE(root_surface->CopyOfOutputRequired());
+    EXPECT_FALSE(root_surface->SubtreeCaptureId().is_valid());
+
+    EndTest();
+  }
+
+  void DisplayReceivedCompositorFrameOnThread(
+      const viz::CompositorFrame& frame) override {
+    // There should be a single compositor render pass, which has no valid
+    // SubtreeCaptureId.
+    ASSERT_EQ(frame.render_pass_list.size(), 1u);
+    EXPECT_FALSE(frame.render_pass_list.back()->subtree_capture_id.is_valid());
+  }
+};
+
+SINGLE_AND_MULTI_THREAD_TEST_F(
+    LayerTreeHostCaptureTestNoExtraRenderPassWhenNotCapturing);
+
+// -----------------------------------------------------------------------------
+// LayerTreeHostCaptureTestLayerWithCaptureIdElevatesToSurface
+//
+// Tests that a layer sub tree whose root has a valid viz::SubtreeCaptureId will
+// draw into a separate render surface and a separate render pass.
+class LayerTreeHostCaptureTestLayerWithCaptureIdElevatesToSurface
+    : public LayerTreeHostCaptureTest {
+ public:
+  void BeginTest() override { child_->SetSubtreeCaptureId(kCaptureId); }
+
+  void VerifyLayerImpls(LayerImpl* root,
+                        LayerImpl* child,
+                        LayerImpl* grand_child) override {
+    // |child| should draw into a separate render surface from that of the root,
+    // and the |grand_child| should draw into the render surface of its parent
+    // (which is |child|'s).
+    // The |chils|'s surface should have the expected capture ID.
+    auto* root_surface = GetRenderSurface(root);
+    auto* child_surface = GetRenderSurface(child);
+    auto* grand_child_surface = GetRenderSurface(grand_child);
+    EXPECT_NE(root_surface, child_surface);
+    EXPECT_NE(root_surface, grand_child_surface);
+    EXPECT_EQ(child_surface, grand_child_surface);
+    EXPECT_EQ(kCaptureId, child_surface->SubtreeCaptureId());
+    EXPECT_TRUE(child_surface->CopyOfOutputRequired());
+
+    EndTest();
+  }
+
+  void DisplayReceivedCompositorFrameOnThread(
+      const viz::CompositorFrame& frame) override {
+    // There should be 2 render passes. The non-root render pass is associated
+    // with the layer subtree rooted at |child| and should have the expected
+    // capture ID.
+    ASSERT_EQ(frame.render_pass_list.size(), 2u);
+    EXPECT_TRUE(frame.render_pass_list.front()->subtree_capture_id.is_valid());
+    EXPECT_EQ(kCaptureId, frame.render_pass_list.front()->subtree_capture_id);
+    EXPECT_FALSE(frame.render_pass_list.back()->subtree_capture_id.is_valid());
+  }
+};
+
+SINGLE_AND_MULTI_THREAD_TEST_F(
+    LayerTreeHostCaptureTestLayerWithCaptureIdElevatesToSurface);
+
+}  // namespace
+}  // namespace cc
diff --git a/cc/trees/occlusion_tracker.cc b/cc/trees/occlusion_tracker.cc
index b3ed41e..2a88893 100644
--- a/cc/trees/occlusion_tracker.cc
+++ b/cc/trees/occlusion_tracker.cc
@@ -196,9 +196,7 @@
   // Readbacks always happen on render targets so we only need to check
   // for readbacks here.
   bool target_is_only_for_copy_request_or_force_render_surface =
-      (finished_target_surface->HasCopyRequest() ||
-       finished_target_surface->ShouldCacheRenderSurface()) &&
-      is_hidden;
+      is_hidden && finished_target_surface->CopyOfOutputRequired();
 
   // If the occlusion within the surface can not be applied to things outside of
   // the surface's subtree, then clear the occlusion here so it won't be used.
diff --git a/cc/trees/property_tree.cc b/cc/trees/property_tree.cc
index d9da2f9..c854377 100644
--- a/cc/trees/property_tree.cc
+++ b/cc/trees/property_tree.cc
@@ -729,20 +729,24 @@
   // Exceptions:
   // 1) Nodes that contribute to copy requests, whether hidden or not, must be
   //    drawn.
-  // 2) Nodes that have a backdrop filter.
-  // 3) Nodes with animating screen space opacity on main thread or pending tree
+  // 2) Nodes that have a valid SubtreeCaptureId, must be drawn so that they can
+  //    be captured by the FrameSinkVideoCapturer.
+  // 3) Nodes that have a backdrop filter.
+  // 4) Nodes with animating screen space opacity on main thread or pending tree
   //    are drawn if their parent is drawn irrespective of their opacity.
-  if (node->has_copy_request || node->cache_render_surface)
+  if (node->has_copy_request || node->cache_render_surface ||
+      node->subtree_capture_id.is_valid()) {
     node->is_drawn = true;
-  else if (EffectiveOpacity(node) == 0.f &&
-           (!node->has_potential_opacity_animation ||
-            property_trees()->is_active) &&
-           node->backdrop_filters.IsEmpty())
+  } else if (EffectiveOpacity(node) == 0.f &&
+             (!node->has_potential_opacity_animation ||
+              property_trees()->is_active) &&
+             node->backdrop_filters.IsEmpty()) {
     node->is_drawn = false;
-  else if (parent_node)
+  } else if (parent_node) {
     node->is_drawn = parent_node->is_drawn;
-  else
+  } else {
     node->is_drawn = true;
+  }
 }
 
 void EffectTree::UpdateEffectChanged(EffectNode* node,
@@ -789,7 +793,8 @@
 
 void EffectTree::UpdateOnlyDrawsVisibleContent(EffectNode* node,
                                                EffectNode* parent_node) {
-  node->only_draws_visible_content = !node->has_copy_request;
+  node->only_draws_visible_content =
+      !node->has_copy_request && !node->subtree_capture_id.is_valid();
   if (parent_node)
     node->only_draws_visible_content &= parent_node->only_draws_visible_content;
   if (!node->backdrop_filters.IsEmpty()) {
diff --git a/cc/trees/property_tree_builder.cc b/cc/trees/property_tree_builder.cc
index d0f5851..e7d040f 100644
--- a/cc/trees/property_tree_builder.cc
+++ b/cc/trees/property_tree_builder.cc
@@ -39,6 +39,7 @@
   int scroll_tree_parent;
   int closest_ancestor_with_cached_render_surface;
   int closest_ancestor_with_copy_request;
+  int closest_ancestor_being_captured;
   SkColor safe_opaque_background_color;
   bool animation_axis_aligned_since_render_target;
   bool not_axis_aligned_since_last_clip;
@@ -389,6 +390,9 @@
   if (layer->mirror_count())
     return RenderSurfaceReason::kMirrored;
 
+  if (layer->subtree_capture_id().is_valid())
+    return RenderSurfaceReason::kSubtreeIsBeingCaptured;
+
   return RenderSurfaceReason::kNone;
 }
 
@@ -455,6 +459,7 @@
   node->stable_id = layer->id();
   node->opacity = layer->opacity();
   node->blend_mode = layer->blend_mode();
+  node->subtree_capture_id = layer->subtree_capture_id();
   node->cache_render_surface = layer->cache_render_surface();
   node->has_copy_request = layer->HasCopyRequest();
   node->filters = layer->filters();
@@ -484,6 +489,10 @@
       layer->HasCopyRequest()
           ? node_id
           : data_from_ancestor.closest_ancestor_with_copy_request;
+  node->closest_ancestor_being_captured_id =
+      layer->subtree_capture_id().is_valid()
+          ? node_id
+          : data_from_ancestor.closest_ancestor_being_captured;
 
   if (layer->HasRoundedCorner()) {
     // This is currently in the local space of the layer and hence in an invalid
@@ -518,6 +527,8 @@
       node->closest_ancestor_with_cached_render_surface_id;
   data_for_children->closest_ancestor_with_copy_request =
       node->closest_ancestor_with_copy_request_id;
+  data_for_children->closest_ancestor_being_captured =
+      node->closest_ancestor_being_captured_id;
   data_for_children->effect_tree_parent = node_id;
   layer->SetEffectTreeIndex(node_id);
 
@@ -717,6 +728,8 @@
       EffectTree::kInvalidNodeId;
   data_for_recursion.closest_ancestor_with_copy_request =
       EffectTree::kInvalidNodeId;
+  data_for_recursion.closest_ancestor_being_captured =
+      EffectTree::kInvalidNodeId;
   data_for_recursion.compound_transform_since_render_target = gfx::Transform();
   data_for_recursion.animation_axis_aligned_since_render_target = true;
   data_for_recursion.not_axis_aligned_since_last_clip = false;
diff --git a/chrome/browser/ui/ash/recording_service_browsertest.cc b/chrome/browser/ui/ash/recording_service_browsertest.cc
index 6d8926b..633d56e 100644
--- a/chrome/browser/ui/ash/recording_service_browsertest.cc
+++ b/chrome/browser/ui/ash/recording_service_browsertest.cc
@@ -180,17 +180,13 @@
   FinishVideoRecordingTest(&test_api);
 }
 
-// This test is currently disabled since it will always fail on the bots for
-// now, since audio is not captured on the bots, and currently window recording
-// captures no video frames, so the resulting video file will always be empty.
-// TODO(crbug.com/1143930): Re-enable this once window capture is working.
-IN_PROC_BROWSER_TEST_F(RecordingServiceBrowserTest, DISABLED_RecordWindow) {
+IN_PROC_BROWSER_TEST_F(RecordingServiceBrowserTest, RecordWindow) {
   ash::CaptureModeTestApi test_api;
   test_api.StartForWindow(/*for_video=*/true);
   auto* generator = GetEventGenerator();
   // Move the mouse cursor above the browser window to select it for window
-  // capture.
-  generator->MoveMouseTo(GetBrowserWindow()->GetBoundsInScreen().CenterPoint());
+  // capture (make sure it doesn't hover over the capture bar).
+  generator->MoveMouseTo(GetBrowserWindow()->GetBoundsInScreen().top_center());
   FinishVideoRecordingTest(&test_api);
 }
 
diff --git a/components/viz/common/BUILD.gn b/components/viz/common/BUILD.gn
index 3bcbec85..ce5490b 100644
--- a/components/viz/common/BUILD.gn
+++ b/components/viz/common/BUILD.gn
@@ -268,6 +268,9 @@
     "surfaces/parent_local_surface_id_allocator.h",
     "surfaces/scoped_surface_id_allocator.cc",
     "surfaces/scoped_surface_id_allocator.h",
+    "surfaces/subtree_capture_id.cc",
+    "surfaces/subtree_capture_id.h",
+    "surfaces/subtree_capture_id_allocator.h",
     "surfaces/surface_id.cc",
     "surfaces/surface_id.h",
     "surfaces/surface_info.cc",
diff --git a/components/viz/common/quads/compositor_render_pass.cc b/components/viz/common/quads/compositor_render_pass.cc
index 0ddff20..96c65f3 100644
--- a/components/viz/common/quads/compositor_render_pass.cc
+++ b/components/viz/common/quads/compositor_render_pass.cc
@@ -88,6 +88,7 @@
     const cc::FilterOperations& filters,
     const cc::FilterOperations& backdrop_filters,
     const base::Optional<gfx::RRectF>& backdrop_filter_bounds,
+    SubtreeCaptureId subtree_capture_id,
     bool has_transparent_background,
     bool cache_render_pass,
     bool has_damage_from_contributing_content,
@@ -101,6 +102,7 @@
   this->filters = filters;
   this->backdrop_filters = backdrop_filters;
   this->backdrop_filter_bounds = backdrop_filter_bounds;
+  this->subtree_capture_id = subtree_capture_id;
   this->has_transparent_background = has_transparent_background;
   this->cache_render_pass = cache_render_pass;
   this->has_damage_from_contributing_content =
@@ -222,8 +224,9 @@
                                                 quad_list.size());
   copy_pass->SetAll(id, output_rect, damage_rect, transform_to_root_target,
                     filters, backdrop_filters, backdrop_filter_bounds,
-                    has_transparent_background, cache_render_pass,
-                    has_damage_from_contributing_content, generate_mipmap);
+                    subtree_capture_id, has_transparent_background,
+                    cache_render_pass, has_damage_from_contributing_content,
+                    generate_mipmap);
 
   if (shared_quad_state_list.empty()) {
     DCHECK(quad_list.empty());
diff --git a/components/viz/common/quads/compositor_render_pass.h b/components/viz/common/quads/compositor_render_pass.h
index c0a6f7b..ca96b4c8 100644
--- a/components/viz/common/quads/compositor_render_pass.h
+++ b/components/viz/common/quads/compositor_render_pass.h
@@ -21,6 +21,7 @@
 #include "components/viz/common/quads/largest_draw_quad.h"
 #include "components/viz/common/quads/quad_list.h"
 #include "components/viz/common/quads/render_pass_internal.h"
+#include "components/viz/common/surfaces/subtree_capture_id.h"
 #include "components/viz/common/viz_common_export.h"
 #include "ui/gfx/display_color_spaces.h"
 #include "ui/gfx/geometry/rect.h"
@@ -67,6 +68,7 @@
               const cc::FilterOperations& filters,
               const cc::FilterOperations& backdrop_filters,
               const base::Optional<gfx::RRectF>& backdrop_filter_bounds,
+              SubtreeCaptureId subtree_capture_id,
               bool has_transparent_background,
               bool cache_render_pass,
               bool has_damage_from_contributing_content,
@@ -85,6 +87,10 @@
   // Uniquely identifies the render pass in the compositor's current frame.
   CompositorRenderPassId id;
 
+  // A unique ID that identifies a layer subtree which produces this render
+  // pass, so that it can be captured by a FrameSinkVideoCapturer.
+  SubtreeCaptureId subtree_capture_id;
+
   // For testing functions.
   // TODO(vmpstr): See if we can clean these up by moving the tests to use
   // AggregatedRenderPasses where appropriate.
diff --git a/components/viz/common/quads/compositor_render_pass_unittest.cc b/components/viz/common/quads/compositor_render_pass_unittest.cc
index 720dfd42..e4d22c5 100644
--- a/components/viz/common/quads/compositor_render_pass_unittest.cc
+++ b/components/viz/common/quads/compositor_render_pass_unittest.cc
@@ -13,6 +13,7 @@
 #include "components/viz/common/quads/aggregated_render_pass.h"
 #include "components/viz/common/quads/compositor_render_pass_draw_quad.h"
 #include "components/viz/common/quads/solid_color_draw_quad.h"
+#include "components/viz/common/surfaces/subtree_capture_id.h"
 #include "testing/gtest/include/gtest/gtest.h"
 #include "third_party/skia/include/effects/SkBlurImageFilter.h"
 #include "ui/gfx/geometry/rect_conversions.h"
@@ -37,6 +38,7 @@
     EXPECT_EQ(expected->filters, actual->filters);
     EXPECT_EQ(expected->backdrop_filters, actual->backdrop_filters);
     EXPECT_EQ(expected->backdrop_filter_bounds, actual->backdrop_filter_bounds);
+    EXPECT_EQ(expected->subtree_capture_id, actual->subtree_capture_id);
     EXPECT_EQ(expected->has_transparent_background,
               actual->has_transparent_background);
     EXPECT_EQ(expected->generate_mipmap, actual->generate_mipmap);
@@ -137,7 +139,7 @@
 
   auto pass = CompositorRenderPass::Create();
   pass->SetAll(id, output_rect, damage_rect, transform_to_root, filters,
-               backdrop_filters, backdrop_filter_bounds,
+               backdrop_filters, backdrop_filter_bounds, SubtreeCaptureId{1u},
                has_transparent_background, cache_render_pass,
                has_damage_from_contributing_content, generate_mipmap);
 
@@ -191,12 +193,12 @@
   bool contrib_generate_mipmap = false;
 
   auto contrib = CompositorRenderPass::Create();
-  contrib->SetAll(contrib_id, contrib_output_rect, contrib_damage_rect,
-                  contrib_transform_to_root, contrib_filters,
-                  contrib_backdrop_filters, contrib_backdrop_filter_bounds,
-                  contrib_has_transparent_background, contrib_cache_render_pass,
-                  contrib_has_damage_from_contributing_content,
-                  contrib_generate_mipmap);
+  contrib->SetAll(
+      contrib_id, contrib_output_rect, contrib_damage_rect,
+      contrib_transform_to_root, contrib_filters, contrib_backdrop_filters,
+      contrib_backdrop_filter_bounds, SubtreeCaptureId{2u},
+      contrib_has_transparent_background, contrib_cache_render_pass,
+      contrib_has_damage_from_contributing_content, contrib_generate_mipmap);
 
   SharedQuadState* contrib_shared_state =
       contrib->CreateAndAppendSharedQuadState();
@@ -247,7 +249,7 @@
 
   auto pass = CompositorRenderPass::Create();
   pass->SetAll(id, output_rect, damage_rect, transform_to_root, filters,
-               backdrop_filters, backdrop_filter_bounds,
+               backdrop_filters, backdrop_filter_bounds, SubtreeCaptureId(),
                has_transparent_background, cache_render_pass,
                has_damage_from_contributing_content, generate_mipmap);
 
diff --git a/components/viz/common/surfaces/subtree_capture_id.cc b/components/viz/common/surfaces/subtree_capture_id.cc
new file mode 100644
index 0000000..6be32be
--- /dev/null
+++ b/components/viz/common/surfaces/subtree_capture_id.cc
@@ -0,0 +1,15 @@
+// Copyright 2020 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/subtree_capture_id.h"
+
+#include "base/strings/stringprintf.h"
+
+namespace viz {
+
+std::string SubtreeCaptureId::ToString() const {
+  return base::StringPrintf("SubtreeCaptureId(%u)", subtree_id_);
+}
+
+}  // namespace viz
diff --git a/components/viz/common/surfaces/subtree_capture_id.h b/components/viz/common/surfaces/subtree_capture_id.h
new file mode 100644
index 0000000..3f92911
--- /dev/null
+++ b/components/viz/common/surfaces/subtree_capture_id.h
@@ -0,0 +1,48 @@
+// Copyright 2020 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_SUBTREE_CAPTURE_ID_H_
+#define COMPONENTS_VIZ_COMMON_SURFACES_SUBTREE_CAPTURE_ID_H_
+
+#include <cstdint>
+#include <string>
+
+#include "components/viz/common/viz_common_export.h"
+
+namespace viz {
+
+// A SubtreeCaptureId uniquely identifies a layer subtree within a
+// CompositorFrameSink, which can be captured independently from the root
+// CompositorFrameSink by the FrameSinkVideoCapturer.
+//
+// Use the SubtreeCaptureIdAllocator to allocate a valid instace of this class.
+class VIZ_COMMON_EXPORT SubtreeCaptureId {
+ public:
+  constexpr SubtreeCaptureId() = default;
+  constexpr explicit SubtreeCaptureId(uint32_t subtree_id)
+      : subtree_id_(subtree_id) {}
+  constexpr SubtreeCaptureId(const SubtreeCaptureId&) = default;
+  SubtreeCaptureId& operator=(const SubtreeCaptureId&) = default;
+  ~SubtreeCaptureId() = default;
+
+  constexpr bool is_valid() const { return subtree_id_ != 0; }
+  constexpr uint32_t subtree_id() const { return subtree_id_; }
+
+  bool operator==(const SubtreeCaptureId& rhs) const {
+    return subtree_id_ == rhs.subtree_id_;
+  }
+  bool operator!=(const SubtreeCaptureId& rhs) const { return !(*this == rhs); }
+  bool operator<(const SubtreeCaptureId& rhs) const {
+    return subtree_id_ < rhs.subtree_id_;
+  }
+
+  std::string ToString() const;
+
+ private:
+  uint32_t subtree_id_ = 0;
+};
+
+}  // namespace viz
+
+#endif  // COMPONENTS_VIZ_COMMON_SURFACES_SUBTREE_CAPTURE_ID_H_
diff --git a/components/viz/common/surfaces/subtree_capture_id_allocator.h b/components/viz/common/surfaces/subtree_capture_id_allocator.h
new file mode 100644
index 0000000..330491f9
--- /dev/null
+++ b/components/viz/common/surfaces/subtree_capture_id_allocator.h
@@ -0,0 +1,33 @@
+// Copyright 2020 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_SUBTREE_CAPTURE_ID_ALLOCATOR_H_
+#define COMPONENTS_VIZ_COMMON_SURFACES_SUBTREE_CAPTURE_ID_ALLOCATOR_H_
+
+#include <cstdint>
+
+#include "components/viz/common/surfaces/subtree_capture_id.h"
+
+namespace viz {
+
+// Generates SubtreeCaptureId's by incrementally increasing the subtree_id's.
+class VIZ_COMMON_EXPORT SubtreeCaptureIdAllocator {
+ public:
+  SubtreeCaptureIdAllocator() = default;
+  SubtreeCaptureIdAllocator(const SubtreeCaptureIdAllocator&) = delete;
+  SubtreeCaptureIdAllocator& operator=(const SubtreeCaptureIdAllocator&) =
+      delete;
+  ~SubtreeCaptureIdAllocator() = default;
+
+  SubtreeCaptureId NextSubtreeCaptureId() {
+    return SubtreeCaptureId(next_subtree_id_++);
+  }
+
+ private:
+  uint32_t next_subtree_id_ = 1u;
+};
+
+}  // namespace viz
+
+#endif  // COMPONENTS_VIZ_COMMON_SURFACES_SUBTREE_CAPTURE_ID_ALLOCATOR_H_
diff --git a/components/viz/host/client_frame_sink_video_capturer.cc b/components/viz/host/client_frame_sink_video_capturer.cc
index b77d91d..405ec19 100644
--- a/components/viz/host/client_frame_sink_video_capturer.cc
+++ b/components/viz/host/client_frame_sink_video_capturer.cc
@@ -72,11 +72,13 @@
 }
 
 void ClientFrameSinkVideoCapturer::ChangeTarget(
-    const base::Optional<FrameSinkId>& frame_sink_id) {
+    const base::Optional<FrameSinkId>& frame_sink_id,
+    SubtreeCaptureId subtree_capture_id) {
   DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
 
   target_ = frame_sink_id;
-  capturer_remote_->ChangeTarget(frame_sink_id);
+  subtree_capture_id_ = subtree_capture_id;
+  capturer_remote_->ChangeTarget(frame_sink_id, subtree_capture_id);
 }
 
 void ClientFrameSinkVideoCapturer::Start(
@@ -192,7 +194,7 @@
   if (auto_throttling_enabled_)
     capturer_remote_->SetAutoThrottlingEnabled(*auto_throttling_enabled_);
   if (target_)
-    capturer_remote_->ChangeTarget(target_);
+    capturer_remote_->ChangeTarget(target_, subtree_capture_id_);
   for (Overlay* overlay : overlays_)
     overlay->EstablishConnection(capturer_remote_.get());
   if (is_started_)
diff --git a/components/viz/host/client_frame_sink_video_capturer.h b/components/viz/host/client_frame_sink_video_capturer.h
index 993a2db..019a5ea 100644
--- a/components/viz/host/client_frame_sink_video_capturer.h
+++ b/components/viz/host/client_frame_sink_video_capturer.h
@@ -13,6 +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/subtree_capture_id.h"
 #include "components/viz/host/viz_host_export.h"
 #include "media/base/video_types.h"
 #include "mojo/public/cpp/bindings/pending_receiver.h"
@@ -83,7 +84,8 @@
                                 const gfx::Size& max_size,
                                 bool use_fixed_aspect_ratio);
   void SetAutoThrottlingEnabled(bool enabled);
-  void ChangeTarget(const base::Optional<FrameSinkId>& frame_sink_id);
+  void ChangeTarget(const base::Optional<FrameSinkId>& frame_sink_id,
+                    SubtreeCaptureId subtree_capture_id);
   void Stop();
   void RequestRefreshFrame();
 
@@ -151,6 +153,7 @@
   base::Optional<ResolutionConstraints> resolution_constraints_;
   base::Optional<bool> auto_throttling_enabled_;
   base::Optional<FrameSinkId> target_;
+  SubtreeCaptureId subtree_capture_id_;
   // 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 7d3a7b3..c66d791c 100644
--- a/components/viz/service/BUILD.gn
+++ b/components/viz/service/BUILD.gn
@@ -161,6 +161,8 @@
     "hit_test/hit_test_manager.cc",
     "hit_test/hit_test_manager.h",
     "surfaces/latest_local_surface_id_lookup_delegate.h",
+    "surfaces/pending_copy_output_request.cc",
+    "surfaces/pending_copy_output_request.h",
     "surfaces/referenced_surface_tracker.cc",
     "surfaces/referenced_surface_tracker.h",
     "surfaces/surface.cc",
diff --git a/components/viz/service/display/display_unittest.cc b/components/viz/service/display/display_unittest.cc
index ff06e95..86d958e 100644
--- a/components/viz/service/display/display_unittest.cc
+++ b/components/viz/service/display/display_unittest.cc
@@ -29,6 +29,7 @@
 #include "components/viz/common/quads/surface_draw_quad.h"
 #include "components/viz/common/surfaces/frame_sink_id.h"
 #include "components/viz/common/surfaces/parent_local_surface_id_allocator.h"
+#include "components/viz/common/surfaces/subtree_capture_id.h"
 #include "components/viz/service/display/aggregated_frame.h"
 #include "components/viz/service/display/delegated_ink_point_renderer_skia.h"
 #include "components/viz/service/display/direct_renderer.h"
@@ -734,8 +735,8 @@
       bd_pass->SetAll(render_pass_id_generator.GenerateNextId(),
                       sub_surface_rect, no_damage, gfx::Transform(),
                       cc::FilterOperations(), backdrop_filters,
-                      gfx::RRectF(gfx::RectF(sub_surface_rect), 0), false,
-                      false, false, false);
+                      gfx::RRectF(gfx::RectF(sub_surface_rect), 0),
+                      SubtreeCaptureId(), false, false, false, false);
       pass_list.push_back(std::move(bd_pass));
 
       CompositorFrame frame = CompositorFrameBuilder()
diff --git a/components/viz/service/display/surface_aggregator_unittest.cc b/components/viz/service/display/surface_aggregator_unittest.cc
index a5a95f40..2dcab90 100644
--- a/components/viz/service/display/surface_aggregator_unittest.cc
+++ b/components/viz/service/display/surface_aggregator_unittest.cc
@@ -32,11 +32,13 @@
 #include "components/viz/common/quads/texture_draw_quad.h"
 #include "components/viz/common/quads/yuv_video_draw_quad.h"
 #include "components/viz/common/surfaces/parent_local_surface_id_allocator.h"
+#include "components/viz/common/surfaces/subtree_capture_id.h"
 #include "components/viz/service/display/aggregated_frame.h"
 #include "components/viz/service/display/display_resource_provider.h"
 #include "components/viz/service/display_embedder/server_shared_bitmap_manager.h"
 #include "components/viz/service/frame_sinks/compositor_frame_sink_support.h"
 #include "components/viz/service/frame_sinks/frame_sink_manager_impl.h"
+#include "components/viz/service/surfaces/pending_copy_output_request.h"
 #include "components/viz/service/surfaces/surface.h"
 #include "components/viz/service/surfaces/surface_manager.h"
 #include "components/viz/test/compositor_frame_helpers.h"
@@ -917,8 +919,8 @@
   CopyOutputRequest* RequestCopyOfOutput() {
     auto copy_request = CopyOutputRequest::CreateStubForTesting();
     auto* copy_request_ptr = copy_request.get();
-    root_sink_->RequestCopyOfOutput(local_surface_id(),
-                                    std::move(copy_request));
+    root_sink_->RequestCopyOfOutput(PendingCopyOutputRequest{
+        local_surface_id(), SubtreeCaptureId(), std::move(copy_request)});
     return copy_request_ptr;
   }
 
@@ -1590,8 +1592,8 @@
                         embedded_local_surface_id, device_scale_factor);
   auto copy_request = CopyOutputRequest::CreateStubForTesting();
   auto* copy_request_ptr = copy_request.get();
-  embedded_support->RequestCopyOfOutput(embedded_local_surface_id,
-                                        std::move(copy_request));
+  embedded_support->RequestCopyOfOutput(
+      {embedded_local_surface_id, SubtreeCaptureId(), std::move(copy_request)});
 
   std::vector<Quad> root_quads = {
       Quad::SolidColorQuad(SK_ColorWHITE, gfx::Rect(5, 5)),
@@ -1743,8 +1745,8 @@
                         embedded_local_surface_id, device_scale_factor);
   auto copy_request(CopyOutputRequest::CreateStubForTesting());
   auto* copy_request_ptr = copy_request.get();
-  embedded_support->RequestCopyOfOutput(embedded_local_surface_id,
-                                        std::move(copy_request));
+  embedded_support->RequestCopyOfOutput(
+      {embedded_local_surface_id, SubtreeCaptureId(), std::move(copy_request)});
 
   ParentLocalSurfaceIdAllocator parent_allocator;
   parent_allocator.GenerateId();
@@ -8862,8 +8864,8 @@
   // Now add a CopyOutputRequest on the child surface, so that the delegated
   // ink metadata does get populated on the aggregated frame.
   auto copy_request = CopyOutputRequest::CreateStubForTesting();
-  child_sink_->RequestCopyOfOutput(child_local_surface_id,
-                                   std::move(copy_request));
+  child_sink_->RequestCopyOfOutput(
+      {child_local_surface_id, SubtreeCaptureId(), std::move(copy_request)});
 
   aggregated_frame = AggregateFrame(root_surface_id);
 
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 e677ecec1..94c8969b 100644
--- a/components/viz/service/frame_sinks/compositor_frame_sink_support.cc
+++ b/components/viz/service/frame_sinks/compositor_frame_sink_support.cc
@@ -256,16 +256,17 @@
   surface_resource_holder_.ReceiveFromChild(resources);
 }
 
-std::vector<std::unique_ptr<CopyOutputRequest>>
+std::vector<PendingCopyOutputRequest>
 CompositorFrameSinkSupport::TakeCopyOutputRequests(
     const LocalSurfaceId& latest_local_id) {
-  std::vector<std::unique_ptr<CopyOutputRequest>> results;
+  std::vector<PendingCopyOutputRequest> results;
   for (auto it = copy_output_requests_.begin();
        it != copy_output_requests_.end();) {
     // Requests with a non-valid local id should be satisfied as soon as
     // possible.
-    if (!it->first.is_valid() || it->first <= latest_local_id) {
-      results.push_back(std::move(it->second));
+    if (!it->local_surface_id.is_valid() ||
+        it->local_surface_id <= latest_local_id) {
+      results.push_back(std::move(*it));
       it = copy_output_requests_.erase(it);
     } else {
       ++it;
@@ -765,10 +766,8 @@
 }
 
 void CompositorFrameSinkSupport::RequestCopyOfOutput(
-    const LocalSurfaceId& local_surface_id,
-    std::unique_ptr<CopyOutputRequest> copy_request) {
-  copy_output_requests_.push_back(
-      std::make_pair(local_surface_id, std::move(copy_request)));
+    PendingCopyOutputRequest pending_copy_output_request) {
+  copy_output_requests_.push_back(std::move(pending_copy_output_request));
   if (last_activated_surface_id_.is_valid()) {
     BeginFrameAck ack;
     ack.has_damage = true;
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 3a5f1fa..82c63a7 100644
--- a/components/viz/service/frame_sinks/compositor_frame_sink_support.h
+++ b/components/viz/service/frame_sinks/compositor_frame_sink_support.h
@@ -6,6 +6,7 @@
 #define COMPONENTS_VIZ_SERVICE_FRAME_SINKS_COMPOSITOR_FRAME_SINK_SUPPORT_H_
 
 #include <memory>
+#include <set>
 #include <vector>
 
 #include "base/callback.h"
@@ -127,7 +128,7 @@
       const std::vector<TransferableResource>& resources) override;
   // Takes the CopyOutputRequests that were requested for a surface with at
   // most |local_surface_id|.
-  std::vector<std::unique_ptr<CopyOutputRequest>> TakeCopyOutputRequests(
+  std::vector<PendingCopyOutputRequest> TakeCopyOutputRequests(
       const LocalSurfaceId& local_surface_id) override;
   void OnFrameTokenChanged(uint32_t frame_token) override;
   void OnSurfaceProcessed(Surface* surface) override;
@@ -179,8 +180,8 @@
   void AttachCaptureClient(CapturableFrameSink::Client* client) override;
   void DetachCaptureClient(CapturableFrameSink::Client* client) override;
   gfx::Size GetActiveFrameSize() override;
-  void RequestCopyOfOutput(const LocalSurfaceId& local_surface_id,
-                           std::unique_ptr<CopyOutputRequest> request) override;
+  void RequestCopyOfOutput(
+      PendingCopyOutputRequest pending_copy_output_request) override;
   const CompositorFrameMetadata* GetLastActivatedFrameMetadata() override;
 
   HitTestAggregator* GetHitTestAggregator();
@@ -197,8 +198,7 @@
   // string.
   static const char* GetSubmitResultAsString(SubmitResult result);
 
-  const std::vector<
-      std::pair<LocalSurfaceId, std::unique_ptr<CopyOutputRequest>>>&
+  const std::vector<PendingCopyOutputRequest>&
   copy_output_requests_for_testing() const {
     return copy_output_requests_;
   }
@@ -313,13 +313,13 @@
   // These are the CopyOutputRequests made on the frame sink (as opposed to
   // being included as a part of a CompositorFrame). They stay here until a
   // Surface with a LocalSurfaceId which is at least the stored LocalSurfaceId
-  // takes them. For example, if we store a pair of LocalSurfaceId stored_id and
-  // a CopyOutputRequest, then a surface with LocalSurfaceId >= stored_id will
-  // take it, but a surface with LocalSurfaceId < stored_id will not. Note that
-  // if stored_id is default initialized, then the next surface will take it
-  // regardless of its LocalSurfaceId.
-  std::vector<std::pair<LocalSurfaceId, std::unique_ptr<CopyOutputRequest>>>
-      copy_output_requests_;
+  // takes them. For example, for a stored PendingCopyOutputRequest, a surface
+  // with LocalSurfaceId >= PendingCopyOutputRequest::local_surface_id will take
+  // it, but a surface with LocalSurfaceId <
+  // PendingCopyOutputRequest::local_surface_id will not. Note that if the
+  // PendingCopyOutputRequest::local_surface_id is default initialized, then the
+  // next surface will take it regardless of its LocalSurfaceId.
+  std::vector<PendingCopyOutputRequest> copy_output_requests_;
 
   mojom::CompositorFrameSink::SubmitCompositorFrameSyncCallback
       compositor_frame_callback_;
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 62b7554..48538474 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
@@ -14,6 +14,7 @@
 #include "components/viz/common/frame_sinks/copy_output_result.h"
 #include "components/viz/common/quads/compositor_frame.h"
 #include "components/viz/common/surfaces/frame_sink_id.h"
+#include "components/viz/common/surfaces/subtree_capture_id.h"
 #include "components/viz/common/surfaces/surface_id.h"
 #include "components/viz/common/surfaces/surface_info.h"
 #include "components/viz/service/display_embedder/server_shared_bitmap_manager.h"
@@ -793,6 +794,60 @@
   std::move(finished).Run();
 }
 
+TEST_F(CompositorFrameSinkSupportTest, CopyRequestOnSubtree) {
+  const SurfaceId surface_id(support_->frame_sink_id(), local_surface_id_);
+
+  constexpr SubtreeCaptureId kSubtreeId1(22);
+  constexpr SubtreeCaptureId kSubtreeId2(44);
+
+  {
+    auto frame = CompositorFrameBuilder()
+                     .AddDefaultRenderPass()
+                     .AddDefaultRenderPass()
+                     .SetReferencedSurfaces({SurfaceRange(surface_id)})
+                     .Build();
+    frame.render_pass_list.front()->subtree_capture_id = kSubtreeId1;
+    support_->SubmitCompositorFrame(local_surface_id_, std::move(frame));
+    EXPECT_EQ(surface_observer_.last_created_surface_id().local_surface_id(),
+              local_surface_id_);
+  }
+
+  // Requesting copy of output of a render pass identifiable by a valid
+  // SubtreeCaptureId.
+  bool called1 = false;
+  base::RunLoop called1_run_loop;
+  auto request = std::make_unique<CopyOutputRequest>(
+      CopyOutputRequest::ResultFormat::RGBA_BITMAP,
+      base::BindOnce(&CopyRequestTestCallback, &called1,
+                     called1_run_loop.QuitClosure()));
+  support_->RequestCopyOfOutput(
+      {local_surface_id_, kSubtreeId1, std::move(request)});
+  GetSurfaceForId(surface_id)->TakeCopyOutputRequestsFromClient();
+  EXPECT_FALSE(called1);
+
+  // Requesting copy of output using a SubtreeCaptureId that has no associated
+  // render pass. The callback will be called immediately.
+  bool called2 = false;
+  base::RunLoop called2_run_loop;
+  request = std::make_unique<CopyOutputRequest>(
+      CopyOutputRequest::ResultFormat::RGBA_BITMAP,
+      base::BindOnce(&CopyRequestTestCallback, &called2,
+                     called2_run_loop.QuitClosure()));
+  support_->RequestCopyOfOutput(
+      {local_surface_id_, kSubtreeId2, std::move(request)});
+  GetSurfaceForId(surface_id)->TakeCopyOutputRequestsFromClient();
+  called2_run_loop.Run();
+  EXPECT_FALSE(called1);
+  EXPECT_TRUE(called2);
+
+  support_->EvictSurface(local_surface_id_);
+  ExpireAllTemporaryReferences();
+  local_surface_id_ = LocalSurfaceId();
+  manager_.surface_manager()->GarbageCollectSurfaces();
+  called1_run_loop.Run();
+  EXPECT_TRUE(called1);
+}
+
 TEST_F(CompositorFrameSinkSupportTest, DuplicateCopyRequest) {
   const SurfaceId surface_id(support_->frame_sink_id(), local_surface_id_);
 
@@ -814,7 +869,8 @@
                      called1_run_loop.QuitClosure()));
   request->set_source(kArbitrarySourceId1);
 
-  support_->RequestCopyOfOutput(local_surface_id_, std::move(request));
+  support_->RequestCopyOfOutput(
+      {local_surface_id_, SubtreeCaptureId(), std::move(request)});
   GetSurfaceForId(surface_id)->TakeCopyOutputRequestsFromClient();
   EXPECT_FALSE(called1);
 
@@ -826,7 +882,8 @@
                      called2_run_loop.QuitClosure()));
   request->set_source(kArbitrarySourceId2);
 
-  support_->RequestCopyOfOutput(local_surface_id_, std::move(request));
+  support_->RequestCopyOfOutput(
+      {local_surface_id_, SubtreeCaptureId(), std::move(request)});
   GetSurfaceForId(surface_id)->TakeCopyOutputRequestsFromClient();
   // Callbacks have different sources so neither should be called.
   EXPECT_FALSE(called1);
@@ -840,7 +897,8 @@
                      called3_run_loop.QuitClosure()));
   request->set_source(kArbitrarySourceId1);
 
-  support_->RequestCopyOfOutput(local_surface_id_, std::move(request));
+  support_->RequestCopyOfOutput(
+      {local_surface_id_, SubtreeCaptureId(), std::move(request)});
   GetSurfaceForId(surface_id)->TakeCopyOutputRequestsFromClient();
   // Two callbacks are from source1, so the first should be called.
   called1_run_loop.Run();
@@ -1065,7 +1123,8 @@
   auto request = std::make_unique<CopyOutputRequest>(
       CopyOutputRequest::ResultFormat::RGBA_BITMAP,
       base::BindOnce(StubResultCallback));
-  support_->RequestCopyOfOutput(local_surface_id1, std::move(request));
+  support_->RequestCopyOfOutput(
+      {local_surface_id1, SubtreeCaptureId(), std::move(request)});
 
   // First surface takes CopyOutputRequests from its client. Now only the first
   // surface should report having CopyOutputRequests.
@@ -1107,7 +1166,8 @@
   auto request = std::make_unique<CopyOutputRequest>(
       CopyOutputRequest::ResultFormat::RGBA_BITMAP,
       base::BindOnce(StubResultCallback));
-  support_->RequestCopyOfOutput(local_surface_id2, std::move(request));
+  support_->RequestCopyOfOutput(
+      {local_surface_id2, SubtreeCaptureId(), std::move(request)});
 
   // The first surface doesn't have copy output requests, because it can't
   // satisfy the request that the client has.
@@ -1148,7 +1208,8 @@
   auto request = std::make_unique<CopyOutputRequest>(
       CopyOutputRequest::ResultFormat::RGBA_BITMAP,
       base::BindOnce(StubResultCallback));
-  support_->RequestCopyOfOutput(local_surface_id1, std::move(request));
+  support_->RequestCopyOfOutput(
+      {local_surface_id1, SubtreeCaptureId(), std::move(request)});
 
   // Create the second surface.
   support_->SubmitCompositorFrame(local_surface_id2,
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 14d8887..b8c4736 100644
--- a/components/viz/service/frame_sinks/frame_sink_manager_impl.cc
+++ b/components/viz/service/frame_sinks/frame_sink_manager_impl.cc
@@ -7,15 +7,19 @@
 #include <stddef.h>
 #include <stdint.h>
 
+#include <utility>
+
 #include "base/bind.h"
 #include "base/check_op.h"
 #include "base/metrics/histogram_functions.h"
 #include "base/trace_event/trace_event.h"
+#include "components/viz/common/surfaces/subtree_capture_id.h"
 #include "components/viz/service/display/shared_bitmap_manager.h"
 #include "components/viz/service/display_embedder/output_surface_provider.h"
 #include "components/viz/service/frame_sinks/compositor_frame_sink_support.h"
 #include "components/viz/service/frame_sinks/video_capture/capturable_frame_sink.h"
 #include "components/viz/service/frame_sinks/video_capture/frame_sink_video_capturer_impl.h"
+#include "components/viz/service/surfaces/pending_copy_output_request.h"
 
 namespace viz {
 
@@ -300,8 +304,8 @@
     // |request| will send an empty result when it goes out of scope.
     return;
   }
-  it->second->RequestCopyOfOutput(surface_id.local_surface_id(),
-                                  std::move(request));
+  it->second->RequestCopyOfOutput(PendingCopyOutputRequest{
+      surface_id.local_surface_id(), SubtreeCaptureId(), std::move(request)});
 }
 
 void FrameSinkManagerImpl::SetHitTestAsyncQueriedDebugRegions(
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 ed9e14e..fb5ec96 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
@@ -8,6 +8,7 @@
 #include <memory>
 
 #include "base/time/time.h"
+#include "components/viz/service/surfaces/pending_copy_output_request.h"
 #include "ui/gfx/geometry/size.h"
 
 namespace gfx {
@@ -17,7 +18,6 @@
 namespace viz {
 
 class CompositorFrameMetadata;
-class CopyOutputRequest;
 class LocalSurfaceId;
 
 // Interface for CompositorFrameSink implementations that support frame sink
@@ -60,8 +60,7 @@
   // default constructed, then the next surface will provide the copy output
   // regardless of its LocalSurfaceId.
   virtual void RequestCopyOfOutput(
-      const LocalSurfaceId& local_surface_id,
-      std::unique_ptr<CopyOutputRequest> request) = 0;
+      PendingCopyOutputRequest pending_copy_output_request) = 0;
 
   // Returns the CompositorFrameMetadata of the last activated CompositorFrame.
   // Return null if no CompositorFrame has activated yet.
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 d5a5739..ce5fca55 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
@@ -226,15 +226,18 @@
 }
 
 void FrameSinkVideoCapturerImpl::ChangeTarget(
-    const base::Optional<FrameSinkId>& frame_sink_id) {
+    const base::Optional<FrameSinkId>& frame_sink_id,
+    const SubtreeCaptureId& subtree_capture_id) {
   DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
 
   if (frame_sink_id) {
     requested_target_ = *frame_sink_id;
+    request_subtree_id_ = subtree_capture_id;
     SetResolvedTarget(
         frame_sink_manager_->FindCapturableFrameSink(requested_target_));
   } else {
     requested_target_ = FrameSinkId();
+    request_subtree_id_ = SubtreeCaptureId();
     SetResolvedTarget(nullptr);
   }
 }
@@ -660,7 +663,8 @@
         request->scale_to().ToString().c_str(), utilization));
   }
 
-  resolved_target_->RequestCopyOfOutput(LocalSurfaceId(), std::move(request));
+  resolved_target_->RequestCopyOfOutput(
+      {LocalSurfaceId(), request_subtree_id_, std::move(request)});
 }
 
 void FrameSinkVideoCapturerImpl::DidCopyFrame(
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 2f0e13f..3319829 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
@@ -9,7 +9,9 @@
 
 #include <memory>
 #include <queue>
+#include <string>
 #include <vector>
+
 #include "base/callback_forward.h"
 #include "base/containers/flat_map.h"
 #include "base/macros.h"
@@ -22,6 +24,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/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/interprocess_frame_pool.h"
@@ -110,7 +113,8 @@
                                 const gfx::Size& max_size,
                                 bool use_fixed_aspect_ratio) final;
   void SetAutoThrottlingEnabled(bool enabled) final;
-  void ChangeTarget(const base::Optional<FrameSinkId>& frame_sink_id) final;
+  void ChangeTarget(const base::Optional<FrameSinkId>& frame_sink_id,
+                    const SubtreeCaptureId& subtree_capture_id) final;
   void Start(mojo::PendingRemote<mojom::FrameSinkVideoConsumer> consumer) final;
   void Stop() final;
   void RequestRefreshFrame() final;
@@ -258,6 +262,12 @@
   // ChangeTarget().
   FrameSinkId requested_target_;
 
+  // If valid, this is the ID of a layer subtree within the requested frame
+  // sink, whose associated render pass should be captured by this capturer.
+  // If not valid, then this capturer capturer the root render pass of the
+  // target frame sink.
+  SubtreeCaptureId request_subtree_id_;
+
   // The resolved target of video capture, or null if the requested target does
   // not yet exist (or no longer exists).
   CapturableFrameSink* resolved_target_ = nullptr;
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 a0094e46..0f427669 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
@@ -19,6 +19,7 @@
 #include "components/viz/common/frame_sinks/copy_output_request.h"
 #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/service/frame_sinks/video_capture/frame_sink_video_capturer_manager.h"
 #include "media/base/limits.h"
 #include "media/base/video_util.h"
@@ -251,8 +252,8 @@
   gfx::Size GetActiveFrameSize() override { return source_size(); }
 
   void RequestCopyOfOutput(
-      const LocalSurfaceId& local_surface_id,
-      std::unique_ptr<CopyOutputRequest> request) override {
+      PendingCopyOutputRequest pending_copy_output_request) override {
+    auto& request = pending_copy_output_request.copy_output_request;
     EXPECT_EQ(CopyOutputResult::Format::I420_PLANES, request->result_format());
     EXPECT_NE(base::UnguessableToken(), request->source());
     EXPECT_EQ(gfx::Rect(size_set_.source_size), request->area());
@@ -522,7 +523,7 @@
       .WillRepeatedly(Return(&frame_sink_));
 
   EXPECT_EQ(FrameSinkId(), capturer_->requested_target());
-  capturer_->ChangeTarget(kFrameSinkId);
+  capturer_->ChangeTarget(kFrameSinkId, SubtreeCaptureId());
   EXPECT_EQ(kFrameSinkId, capturer_->requested_target());
   EXPECT_EQ(capturer_.get(), frame_sink_.attached_client());
 }
@@ -534,7 +535,7 @@
       .WillRepeatedly(Return(nullptr));
 
   EXPECT_EQ(FrameSinkId(), capturer_->requested_target());
-  capturer_->ChangeTarget(kFrameSinkId);
+  capturer_->ChangeTarget(kFrameSinkId, SubtreeCaptureId());
   EXPECT_EQ(kFrameSinkId, capturer_->requested_target());
   EXPECT_EQ(nullptr, frame_sink_.attached_client());
 
@@ -569,7 +570,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);
+  capturer_->ChangeTarget(kFrameSinkId, SubtreeCaptureId());
   EXPECT_EQ(1, frame_sink_.num_copy_results());
   EXPECT_FALSE(IsRefreshRetryTimerRunning());
 
@@ -585,7 +586,7 @@
   EXPECT_CALL(frame_sink_manager_, FindCapturableFrameSink(kFrameSinkId))
       .WillRepeatedly(Return(&frame_sink_));
 
-  capturer_->ChangeTarget(kFrameSinkId);
+  capturer_->ChangeTarget(kFrameSinkId, SubtreeCaptureId());
   EXPECT_FALSE(IsRefreshRetryTimerRunning());
 
   MockConsumer consumer;
@@ -678,7 +679,7 @@
   EXPECT_CALL(frame_sink_manager_, FindCapturableFrameSink(kFrameSinkId))
       .WillRepeatedly(Return(&frame_sink_));
 
-  capturer_->ChangeTarget(kFrameSinkId);
+  capturer_->ChangeTarget(kFrameSinkId, SubtreeCaptureId());
 
   NiceMock<MockConsumer> consumer;
   StartCapture(&consumer);
@@ -776,7 +777,7 @@
   EXPECT_CALL(frame_sink_manager_, FindCapturableFrameSink(kFrameSinkId))
       .WillRepeatedly(Return(&frame_sink_));
 
-  capturer_->ChangeTarget(kFrameSinkId);
+  capturer_->ChangeTarget(kFrameSinkId, SubtreeCaptureId());
 
   NiceMock<MockConsumer> consumer;
   StartCapture(&consumer);
@@ -833,7 +834,7 @@
   EXPECT_CALL(frame_sink_manager_, FindCapturableFrameSink(kFrameSinkId))
       .WillRepeatedly(Return(&frame_sink_));
 
-  capturer_->ChangeTarget(kFrameSinkId);
+  capturer_->ChangeTarget(kFrameSinkId, SubtreeCaptureId());
 
   // Start capturing to the first consumer.
   MockConsumer consumer;
@@ -920,7 +921,7 @@
   EXPECT_CALL(frame_sink_manager_, FindCapturableFrameSink(kFrameSinkId))
       .WillRepeatedly(Return(&frame_sink_));
 
-  capturer_->ChangeTarget(kFrameSinkId);
+  capturer_->ChangeTarget(kFrameSinkId, SubtreeCaptureId());
 
   MockConsumer consumer;
   const int num_refresh_frames = 2;  // Initial, plus later refresh.
@@ -976,7 +977,7 @@
   EXPECT_CALL(frame_sink_manager_, FindCapturableFrameSink(kFrameSinkId))
       .WillRepeatedly(Return(&frame_sink_));
 
-  capturer_->ChangeTarget(kFrameSinkId);
+  capturer_->ChangeTarget(kFrameSinkId, SubtreeCaptureId());
 
   MockConsumer consumer;
   constexpr int num_refresh_frames = 3;  // Initial, plus two refreshes after
@@ -1073,7 +1074,7 @@
 TEST_F(FrameSinkVideoCapturerTest, CompositorFrameMetadataReachesConsumer) {
   EXPECT_CALL(frame_sink_manager_, FindCapturableFrameSink(kFrameSinkId))
       .WillRepeatedly(Return(&frame_sink_));
-  capturer_->ChangeTarget(kFrameSinkId);
+  capturer_->ChangeTarget(kFrameSinkId, SubtreeCaptureId());
 
   MockConsumer consumer;
   // Initial refresh frame for starting capture, plus later refresh.
@@ -1133,7 +1134,7 @@
 TEST_F(FrameSinkVideoCapturerTest, DeliversUpdateRectAndCaptureCounter) {
   EXPECT_CALL(frame_sink_manager_, FindCapturableFrameSink(kFrameSinkId))
       .WillRepeatedly(Return(&frame_sink_));
-  capturer_->ChangeTarget(kFrameSinkId);
+  capturer_->ChangeTarget(kFrameSinkId, SubtreeCaptureId());
 
   MockConsumer consumer;
   StartCapture(&consumer);
@@ -1248,7 +1249,7 @@
 TEST_F(FrameSinkVideoCapturerTest, CaptureCounterSkipsWhenFramesAreDropped) {
   EXPECT_CALL(frame_sink_manager_, FindCapturableFrameSink(kFrameSinkId))
       .WillRepeatedly(Return(&frame_sink_));
-  capturer_->ChangeTarget(kFrameSinkId);
+  capturer_->ChangeTarget(kFrameSinkId, SubtreeCaptureId());
 
   MockConsumer consumer;
   StartCapture(&consumer);
diff --git a/components/viz/service/surfaces/pending_copy_output_request.cc b/components/viz/service/surfaces/pending_copy_output_request.cc
new file mode 100644
index 0000000..a541a993
--- /dev/null
+++ b/components/viz/service/surfaces/pending_copy_output_request.cc
@@ -0,0 +1,27 @@
+// Copyright 2020 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/service/surfaces/pending_copy_output_request.h"
+
+#include <utility>
+
+namespace viz {
+
+PendingCopyOutputRequest::PendingCopyOutputRequest(
+    LocalSurfaceId surface_id,
+    SubtreeCaptureId subtree_id,
+    std::unique_ptr<CopyOutputRequest> request)
+    : local_surface_id(surface_id),
+      subtree_capture_id(subtree_id),
+      copy_output_request(std::move(request)) {}
+
+PendingCopyOutputRequest::PendingCopyOutputRequest(PendingCopyOutputRequest&&) =
+    default;
+
+PendingCopyOutputRequest& PendingCopyOutputRequest::operator=(
+    PendingCopyOutputRequest&&) = default;
+
+PendingCopyOutputRequest::~PendingCopyOutputRequest() = default;
+
+}  // namespace viz
diff --git a/components/viz/service/surfaces/pending_copy_output_request.h b/components/viz/service/surfaces/pending_copy_output_request.h
new file mode 100644
index 0000000..4783a5d
--- /dev/null
+++ b/components/viz/service/surfaces/pending_copy_output_request.h
@@ -0,0 +1,43 @@
+// Copyright 2020 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_SERVICE_SURFACES_PENDING_COPY_OUTPUT_REQUEST_H_
+#define COMPONENTS_VIZ_SERVICE_SURFACES_PENDING_COPY_OUTPUT_REQUEST_H_
+
+#include <memory>
+
+#include "components/viz/common/frame_sinks/copy_output_request.h"
+#include "components/viz/common/surfaces/local_surface_id.h"
+#include "components/viz/common/surfaces/subtree_capture_id.h"
+#include "components/viz/service/viz_service_export.h"
+
+namespace viz {
+
+// Encapsulates the necessary parameters to request a copy-of-output on a
+// surface.
+struct VIZ_SERVICE_EXPORT PendingCopyOutputRequest {
+  PendingCopyOutputRequest(LocalSurfaceId surface_id,
+                           SubtreeCaptureId subtree_id,
+                           std::unique_ptr<CopyOutputRequest> request);
+  PendingCopyOutputRequest(PendingCopyOutputRequest&&);
+  PendingCopyOutputRequest& operator=(PendingCopyOutputRequest&&);
+  ~PendingCopyOutputRequest();
+
+  // The ID of the local surface which |copy_output_request| will be placed on
+  // its next composited frame. If this ID is default constructed, then the next
+  // surface will provide the copy-of-output regardless of its LocalSurfaceId.
+  LocalSurfaceId local_surface_id;
+
+  // If valid, the |copy_output_request| will be placed on a render pass
+  // associated with a layer subtree identified by this ID. Otherwise, it will
+  // be placed on the root render pass.
+  SubtreeCaptureId subtree_capture_id;
+
+  // The actual copy-of-output request.
+  std::unique_ptr<CopyOutputRequest> copy_output_request;
+};
+
+}  // namespace viz
+
+#endif  // COMPONENTS_VIZ_SERVICE_SURFACES_PENDING_COPY_OUTPUT_REQUEST_H_
diff --git a/components/viz/service/surfaces/surface.cc b/components/viz/service/surfaces/surface.cc
index ae5ea2e..e25a769a 100644
--- a/components/viz/service/surfaces/surface.cc
+++ b/components/viz/service/surfaces/surface.cc
@@ -15,7 +15,7 @@
 #include "base/stl_util.h"
 #include "base/time/tick_clock.h"
 #include "base/trace_event/trace_event.h"
-#include "components/viz/common/frame_sinks/copy_output_request.h"
+#include "components/viz/common/quads/compositor_render_pass.h"
 #include "components/viz/common/resources/returned_resource.h"
 #include "components/viz/common/resources/transferable_resource.h"
 #include "components/viz/common/surfaces/parent_local_surface_id_allocator.h"
@@ -29,6 +29,26 @@
 
 namespace viz {
 
+namespace {
+
+// Adds the given |request| to the requests of the given |render_pass|, removing
+// any duplicate requests made by the same source.
+void RequestCopyOfOutputOnRenderPass(std::unique_ptr<CopyOutputRequest> request,
+                                     CompositorRenderPass& render_pass) {
+  if (request->has_source()) {
+    const base::UnguessableToken& source = request->source();
+    // Remove existing CopyOutputRequests made on the Surface by the same
+    // source.
+    base::EraseIf(render_pass.copy_requests,
+                  [&source](const std::unique_ptr<CopyOutputRequest>& x) {
+                    return x->has_source() && x->source() == source;
+                  });
+  }
+  render_pass.copy_requests.push_back(std::move(request));
+}
+
+}  // namespace
+
 Surface::PresentationHelper::PresentationHelper(
     base::WeakPtr<SurfaceClient> surface_client,
     uint32_t frame_token)
@@ -262,25 +282,40 @@
 }
 
 void Surface::RequestCopyOfOutput(
-    std::unique_ptr<CopyOutputRequest> copy_request) {
+    PendingCopyOutputRequest pending_copy_output_request) {
   TRACE_EVENT1("viz", "Surface::RequestCopyOfOutput", "has_active_frame_data",
                !!active_frame_data_);
+
+  if (!pending_copy_output_request.subtree_capture_id.is_valid()) {
+    RequestCopyOfOutputOnRootRenderPass(
+        std::move(pending_copy_output_request.copy_output_request));
+    return;
+  }
+
+  if (!active_frame_data_)
+    return;
+
+  for (auto& render_pass : active_frame_data_->frame.render_pass_list) {
+    if (render_pass->subtree_capture_id ==
+        pending_copy_output_request.subtree_capture_id) {
+      RequestCopyOfOutputOnRenderPass(
+          std::move(pending_copy_output_request.copy_output_request),
+          *render_pass);
+      return;
+    }
+  }
+}
+
+void Surface::RequestCopyOfOutputOnRootRenderPass(
+    std::unique_ptr<CopyOutputRequest> copy_request) {
+  TRACE_EVENT1("viz", "Surface::RequestCopyOfOutputOnRootRenderPass",
+               "has_active_frame_data", !!active_frame_data_);
   if (!active_frame_data_)
     return;  // |copy_request| auto-sends empty result on out-of-scope.
 
-  std::vector<std::unique_ptr<CopyOutputRequest>>& copy_requests =
-      active_frame_data_->frame.render_pass_list.back()->copy_requests;
-
-  if (copy_request->has_source()) {
-    const base::UnguessableToken& source = copy_request->source();
-    // Remove existing CopyOutputRequests made on the Surface by the same
-    // source.
-    base::EraseIf(copy_requests,
-                  [&source](const std::unique_ptr<CopyOutputRequest>& x) {
-                    return x->has_source() && x->source() == source;
-                  });
-  }
-  copy_requests.push_back(std::move(copy_request));
+  RequestCopyOfOutputOnRenderPass(
+      std::move(copy_request),
+      *active_frame_data_->frame.render_pass_list.back());
 }
 
 void Surface::OnActivationDependencyResolved(
@@ -425,7 +460,7 @@
   RecomputeActiveReferencedSurfaces();
 
   for (auto& copy_request : old_copy_requests)
-    RequestCopyOfOutput(std::move(copy_request));
+    RequestCopyOfOutputOnRootRenderPass(std::move(copy_request));
 
   UnrefFrameResourcesAndRunCallbacks(std::move(previous_frame_data));
 
@@ -557,10 +592,10 @@
 void Surface::TakeCopyOutputRequestsFromClient() {
   if (!surface_client_)
     return;
-  for (std::unique_ptr<CopyOutputRequest>& request :
+  for (PendingCopyOutputRequest& request_params :
        surface_client_->TakeCopyOutputRequests(
            surface_id().local_surface_id())) {
-    RequestCopyOfOutput(std::move(request));
+    RequestCopyOfOutput(std::move(request_params));
   }
 }
 
diff --git a/components/viz/service/surfaces/surface.h b/components/viz/service/surfaces/surface.h
index cdf3329..cdc8c82 100644
--- a/components/viz/service/surfaces/surface.h
+++ b/components/viz/service/surfaces/surface.h
@@ -25,6 +25,7 @@
 #include "components/viz/common/quads/compositor_frame.h"
 #include "components/viz/common/surfaces/frame_sink_id.h"
 #include "components/viz/common/surfaces/surface_info.h"
+#include "components/viz/service/surfaces/pending_copy_output_request.h"
 #include "components/viz/service/surfaces/surface_client.h"
 #include "components/viz/service/surfaces/surface_dependency_deadline.h"
 #include "components/viz/service/viz_service_export.h"
@@ -311,7 +312,15 @@
       CompositorFrame* frame,
       std::vector<ui::LatencyInfo>* latency_info);
 
-  void RequestCopyOfOutput(std::unique_ptr<CopyOutputRequest> copy_request);
+  // Places the copy-of-output request on the render pass defined by
+  // |PendingCopyOutputRequest::subtree_capture_id| if such a render pass
+  // exists, otherwise the request will be ignored.
+  void RequestCopyOfOutput(
+      PendingCopyOutputRequest pending_copy_output_request);
+
+  // Always placed the given |copy_request| on the root render pass.
+  void RequestCopyOfOutputOnRootRenderPass(
+      std::unique_ptr<CopyOutputRequest> copy_request);
 
   const SurfaceInfo surface_info_;
   SurfaceId previous_frame_surface_id_;
diff --git a/components/viz/service/surfaces/surface_client.h b/components/viz/service/surfaces/surface_client.h
index b902e91..d85bca8 100644
--- a/components/viz/service/surfaces/surface_client.h
+++ b/components/viz/service/surfaces/surface_client.h
@@ -9,6 +9,7 @@
 #include <vector>
 
 #include "base/macros.h"
+#include "components/viz/service/surfaces/pending_copy_output_request.h"
 #include "components/viz/service/viz_service_export.h"
 
 namespace base {
@@ -24,7 +25,6 @@
 namespace viz {
 struct ReturnedResource;
 class CompositorFrame;
-class CopyOutputRequest;
 class LocalSurfaceId;
 class Surface;
 struct TransferableResource;
@@ -64,8 +64,8 @@
 
   // Takes all the CopyOutputRequests made at the client level that happened for
   // a LocalSurfaceId preceeding the given one.
-  virtual std::vector<std::unique_ptr<CopyOutputRequest>>
-  TakeCopyOutputRequests(const LocalSurfaceId& latest_surface_id) = 0;
+  virtual std::vector<PendingCopyOutputRequest> TakeCopyOutputRequests(
+      const LocalSurfaceId& latest_surface_id) = 0;
 
   // Notifies the client that a frame with |token| has been activated.
   virtual void OnFrameTokenChanged(uint32_t frame_token) = 0;
diff --git a/components/viz/service/surfaces/surface_unittest.cc b/components/viz/service/surfaces/surface_unittest.cc
index abd259a..6fce1d1 100644
--- a/components/viz/service/surfaces/surface_unittest.cc
+++ b/components/viz/service/surfaces/surface_unittest.cc
@@ -4,15 +4,17 @@
 
 #include <utility>
 
-#include "components/viz/service/surfaces/surface.h"
 #include "base/bind.h"
 #include "base/run_loop.h"
 #include "cc/test/scheduler_test_common.h"
 #include "components/viz/common/frame_sinks/copy_output_result.h"
 #include "components/viz/common/surfaces/parent_local_surface_id_allocator.h"
+#include "components/viz/common/surfaces/subtree_capture_id.h"
 #include "components/viz/service/display_embedder/server_shared_bitmap_manager.h"
 #include "components/viz/service/frame_sinks/compositor_frame_sink_support.h"
 #include "components/viz/service/frame_sinks/frame_sink_manager_impl.h"
+#include "components/viz/service/surfaces/pending_copy_output_request.h"
+#include "components/viz/service/surfaces/surface.h"
 #include "components/viz/test/begin_frame_args_test.h"
 #include "components/viz/test/compositor_frame_helpers.h"
 #include "components/viz/test/fake_external_begin_frame_source.h"
@@ -100,12 +102,12 @@
 
   bool copy_called = false;
   base::RunLoop copy_runloop;
-  support->RequestCopyOfOutput(
-      local_surface_id,
+  support->RequestCopyOfOutput(PendingCopyOutputRequest{
+      local_surface_id, SubtreeCaptureId(),
       std::make_unique<CopyOutputRequest>(
           CopyOutputRequest::ResultFormat::RGBA_BITMAP,
           base::BindOnce(&TestCopyResultCallback, &copy_called,
-                         copy_runloop.QuitClosure())));
+                         copy_runloop.QuitClosure()))});
   surface->TakeCopyOutputRequestsFromClient();
   EXPECT_TRUE(surface_manager->GetSurfaceForId(surface_id));
   EXPECT_FALSE(copy_called);
diff --git a/components/viz/test/stub_surface_client.cc b/components/viz/test/stub_surface_client.cc
index d880e05..bb33424 100644
--- a/components/viz/test/stub_surface_client.cc
+++ b/components/viz/test/stub_surface_client.cc
@@ -12,10 +12,9 @@
 
 StubSurfaceClient::~StubSurfaceClient() = default;
 
-std::vector<std::unique_ptr<CopyOutputRequest>>
-StubSurfaceClient::TakeCopyOutputRequests(
+std::vector<PendingCopyOutputRequest> StubSurfaceClient::TakeCopyOutputRequests(
     const LocalSurfaceId& latest_surface_id) {
-  return std::vector<std::unique_ptr<CopyOutputRequest>>();
+  return std::vector<PendingCopyOutputRequest>();
 }
 
 }  // namespace viz
diff --git a/components/viz/test/stub_surface_client.h b/components/viz/test/stub_surface_client.h
index 627a366..a6dc7df 100644
--- a/components/viz/test/stub_surface_client.h
+++ b/components/viz/test/stub_surface_client.h
@@ -27,7 +27,7 @@
       const std::vector<ReturnedResource>& resources) override {}
   void ReceiveFromChild(
       const std::vector<TransferableResource>& resources) override {}
-  std::vector<std::unique_ptr<CopyOutputRequest>> TakeCopyOutputRequests(
+  std::vector<PendingCopyOutputRequest> TakeCopyOutputRequests(
       const LocalSurfaceId& latest_surface_id) override;
   void OnFrameTokenChanged(uint32_t frame_token) override {}
   void OnSurfaceProcessed(Surface* surface) override {}
diff --git a/content/browser/compositor/test/test_image_transport_factory.cc b/content/browser/compositor/test/test_image_transport_factory.cc
index 7650eac..4559caf 100644
--- a/content/browser/compositor/test/test_image_transport_factory.cc
+++ b/content/browser/compositor/test/test_image_transport_factory.cc
@@ -88,6 +88,10 @@
   return frame_sink_id_allocator_.NextFrameSinkId();
 }
 
+viz::SubtreeCaptureId TestImageTransportFactory::AllocateSubtreeCaptureId() {
+  return subtree_capture_id_allocator_.NextSubtreeCaptureId();
+}
+
 viz::HostFrameSinkManager*
 TestImageTransportFactory::GetHostFrameSinkManager() {
   return &host_frame_sink_manager_;
diff --git a/content/browser/compositor/test/test_image_transport_factory.h b/content/browser/compositor/test/test_image_transport_factory.h
index 87a9f09..2b47278 100644
--- a/content/browser/compositor/test/test_image_transport_factory.h
+++ b/content/browser/compositor/test/test_image_transport_factory.h
@@ -13,6 +13,7 @@
 #include "cc/test/test_task_graph_runner.h"
 #include "components/viz/common/display/renderer_settings.h"
 #include "components/viz/common/surfaces/frame_sink_id_allocator.h"
+#include "components/viz/common/surfaces/subtree_capture_id_allocator.h"
 #include "components/viz/host/host_frame_sink_manager.h"
 #include "components/viz/test/test_frame_sink_manager.h"
 #include "components/viz/test/test_gpu_memory_buffer_manager.h"
@@ -44,6 +45,7 @@
   gpu::GpuMemoryBufferManager* GetGpuMemoryBufferManager() override;
   cc::TaskGraphRunner* GetTaskGraphRunner() override;
   viz::FrameSinkId AllocateFrameSinkId() override;
+  viz::SubtreeCaptureId AllocateSubtreeCaptureId() override;
   viz::HostFrameSinkManager* GetHostFrameSinkManager() override;
 
   // ImageTransportFactory implementation.
@@ -56,6 +58,7 @@
   viz::TestGpuMemoryBufferManager gpu_memory_buffer_manager_;
   viz::RendererSettings renderer_settings_;
   viz::FrameSinkIdAllocator frame_sink_id_allocator_;
+  viz::SubtreeCaptureIdAllocator subtree_capture_id_allocator_;
   scoped_refptr<viz::ContextProvider> shared_main_context_provider_;
   viz::HostFrameSinkManager host_frame_sink_manager_;
   viz::TestFrameSinkManagerImpl test_frame_sink_manager_impl_;
diff --git a/content/browser/compositor/viz_process_transport_factory.cc b/content/browser/compositor/viz_process_transport_factory.cc
index 8532a82..d22a77a 100644
--- a/content/browser/compositor/viz_process_transport_factory.cc
+++ b/content/browser/compositor/viz_process_transport_factory.cc
@@ -281,6 +281,10 @@
   return frame_sink_id_allocator_.NextFrameSinkId();
 }
 
+viz::SubtreeCaptureId VizProcessTransportFactory::AllocateSubtreeCaptureId() {
+  return subtree_capture_id_allocator_.NextSubtreeCaptureId();
+}
+
 viz::HostFrameSinkManager*
 VizProcessTransportFactory::GetHostFrameSinkManager() {
   return host_frame_sink_manager_;
diff --git a/content/browser/compositor/viz_process_transport_factory.h b/content/browser/compositor/viz_process_transport_factory.h
index 8097090c..1c34598 100644
--- a/content/browser/compositor/viz_process_transport_factory.h
+++ b/content/browser/compositor/viz_process_transport_factory.h
@@ -10,6 +10,7 @@
 #include "base/macros.h"
 #include "build/build_config.h"
 #include "components/viz/common/surfaces/frame_sink_id_allocator.h"
+#include "components/viz/common/surfaces/subtree_capture_id_allocator.h"
 #include "components/viz/service/main/viz_compositor_thread_runner_impl.h"
 #include "content/browser/compositor/image_transport_factory.h"
 #include "gpu/command_buffer/common/context_result.h"
@@ -70,6 +71,7 @@
   gpu::GpuMemoryBufferManager* GetGpuMemoryBufferManager() override;
   cc::TaskGraphRunner* GetTaskGraphRunner() override;
   viz::FrameSinkId AllocateFrameSinkId() override;
+  viz::SubtreeCaptureId AllocateSubtreeCaptureId() override;
   viz::HostFrameSinkManager* GetHostFrameSinkManager() override;
 
   // ImageTransportFactory implementation.
@@ -139,6 +141,7 @@
   base::flat_map<ui::Compositor*, CompositorData> compositor_data_map_;
 
   viz::FrameSinkIdAllocator frame_sink_id_allocator_;
+  viz::SubtreeCaptureIdAllocator subtree_capture_id_allocator_;
   viz::HostFrameSinkManager* const host_frame_sink_manager_;
 
   scoped_refptr<base::SingleThreadTaskRunner> const resize_task_runner_;
diff --git a/content/browser/devtools/devtools_video_consumer.cc b/content/browser/devtools/devtools_video_consumer.cc
index 8ca2835..32095bf 100644
--- a/content/browser/devtools/devtools_video_consumer.cc
+++ b/content/browser/devtools/devtools_video_consumer.cc
@@ -8,7 +8,9 @@
 
 #include "base/bind.h"
 #include "base/memory/shared_memory_mapping.h"
+#include "base/optional.h"
 #include "cc/paint/skia_paint_canvas.h"
+#include "components/viz/common/surfaces/subtree_capture_id.h"
 #include "components/viz/host/host_frame_sink_manager.h"
 #include "content/browser/compositor/surface_utils.h"
 #include "media/base/limits.h"
@@ -84,10 +86,11 @@
     const viz::FrameSinkId& frame_sink_id) {
   frame_sink_id_ = frame_sink_id;
   if (capturer_) {
-    if (frame_sink_id_.is_valid())
-      capturer_->ChangeTarget(frame_sink_id_);
-    else
-      capturer_->ChangeTarget(base::nullopt);
+    capturer_->ChangeTarget(
+        frame_sink_id_.is_valid()
+            ? base::make_optional<viz::FrameSinkId>(frame_sink_id_)
+            : base::nullopt,
+        viz::SubtreeCaptureId());
   }
 }
 
@@ -129,7 +132,7 @@
                                       kDefaultUseFixedAspectRatio);
   capturer_->SetFormat(pixel_format_, color_space_);
   if (frame_sink_id_.is_valid())
-    capturer_->ChangeTarget(frame_sink_id_);
+    capturer_->ChangeTarget(frame_sink_id_, viz::SubtreeCaptureId());
 
   capturer_->Start(this);
 }
diff --git a/content/browser/devtools/devtools_video_consumer_unittest.cc b/content/browser/devtools/devtools_video_consumer_unittest.cc
index 7d4f41d..58bd2e96 100644
--- a/content/browser/devtools/devtools_video_consumer_unittest.cc
+++ b/content/browser/devtools/devtools_video_consumer_unittest.cc
@@ -80,8 +80,8 @@
                     bool use_fixed_aspect_ratio));
   // This is never called.
   MOCK_METHOD1(SetAutoThrottlingEnabled, void(bool));
-  void ChangeTarget(
-      const base::Optional<viz::FrameSinkId>& frame_sink_id) final {
+  void ChangeTarget(const base::Optional<viz::FrameSinkId>& frame_sink_id,
+                    const viz::SubtreeCaptureId& subtree_capture_id) final {
     frame_sink_id_ = frame_sink_id ? *frame_sink_id : viz::FrameSinkId();
     MockChangeTarget(frame_sink_id_);
   }
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 08bfe13..8b00413 100644
--- a/content/browser/media/capture/frame_sink_video_capture_device.cc
+++ b/content/browser/media/capture/frame_sink_video_capture_device.cc
@@ -17,6 +17,7 @@
 #include "base/threading/thread_task_runner_handle.h"
 #include "base/time/time.h"
 #include "build/build_config.h"
+#include "components/viz/common/surfaces/subtree_capture_id.h"
 #include "components/viz/host/host_frame_sink_manager.h"
 #include "content/browser/compositor/surface_utils.h"
 #include "content/public/browser/browser_task_traits.h"
@@ -120,7 +121,7 @@
                                       constraints.fixed_aspect_ratio);
 
   if (target_.is_valid()) {
-    capturer_->ChangeTarget(target_);
+    capturer_->ChangeTarget(target_, viz::SubtreeCaptureId());
   }
 
 #if !defined(OS_ANDROID)
@@ -300,11 +301,10 @@
 
   target_ = frame_sink_id;
   if (capturer_) {
-    if (target_.is_valid()) {
-      capturer_->ChangeTarget(target_);
-    } else {
-      capturer_->ChangeTarget(base::nullopt);
-    }
+    capturer_->ChangeTarget(target_.is_valid()
+                                ? base::make_optional<viz::FrameSinkId>(target_)
+                                : base::nullopt,
+                            viz::SubtreeCaptureId());
   }
 }
 
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 7589cce..38dca34bd 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
@@ -101,8 +101,8 @@
                     const gfx::Size& max_size,
                     bool use_fixed_aspect_ratio));
   MOCK_METHOD1(SetAutoThrottlingEnabled, void(bool));
-  void ChangeTarget(
-      const base::Optional<viz::FrameSinkId>& frame_sink_id) final {
+  void ChangeTarget(const base::Optional<viz::FrameSinkId>& frame_sink_id,
+                    const viz::SubtreeCaptureId& subtree_capture_id) final {
     DCHECK_NOT_ON_DEVICE_THREAD();
     MockChangeTarget(frame_sink_id ? *frame_sink_id : viz::FrameSinkId());
   }
diff --git a/content/browser/media/capture/slow_window_capturer_chromeos.cc b/content/browser/media/capture/slow_window_capturer_chromeos.cc
index c52d00d..98014b68 100644
--- a/content/browser/media/capture/slow_window_capturer_chromeos.cc
+++ b/content/browser/media/capture/slow_window_capturer_chromeos.cc
@@ -101,7 +101,8 @@
 }
 
 void SlowWindowCapturerChromeOS::ChangeTarget(
-    const base::Optional<viz::FrameSinkId>& frame_sink_id) {
+    const base::Optional<viz::FrameSinkId>& frame_sink_id,
+    const viz::SubtreeCaptureId& subtree_capture_id) {
   // The SlowWindowCapturerChromeOS does not capture from compositor frame
   // sinks.
 }
diff --git a/content/browser/media/capture/slow_window_capturer_chromeos.h b/content/browser/media/capture/slow_window_capturer_chromeos.h
index 6b402d1..3a2ec7f7 100644
--- a/content/browser/media/capture/slow_window_capturer_chromeos.h
+++ b/content/browser/media/capture/slow_window_capturer_chromeos.h
@@ -67,8 +67,8 @@
                                 const gfx::Size& max_size,
                                 bool use_fixed_aspect_ratio) final;
   void SetAutoThrottlingEnabled(bool enabled) final;
-  void ChangeTarget(
-      const base::Optional<viz::FrameSinkId>& frame_sink_id) final;
+  void ChangeTarget(const base::Optional<viz::FrameSinkId>& frame_sink_id,
+                    const viz::SubtreeCaptureId& subtree_capture_id) final;
   void Start(
       mojo::PendingRemote<viz::mojom::FrameSinkVideoConsumer> consumer) final;
   void Stop() final;
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 e8c6d3e8..7a90002e 100644
--- a/content/browser/renderer_host/render_widget_host_view_base.cc
+++ b/content/browser/renderer_host/render_widget_host_view_base.cc
@@ -9,6 +9,7 @@
 #include "base/unguessable_token.h"
 #include "build/build_config.h"
 #include "components/viz/common/features.h"
+#include "components/viz/common/surfaces/subtree_capture_id.h"
 #include "components/viz/host/host_frame_sink_manager.h"
 #include "content/browser/compositor/surface_utils.h"
 #include "content/browser/gpu/gpu_data_manager_impl.h"
@@ -275,7 +276,7 @@
 RenderWidgetHostViewBase::CreateVideoCapturer() {
   std::unique_ptr<viz::ClientFrameSinkVideoCapturer> video_capturer =
       GetHostFrameSinkManager()->CreateVideoCapturer();
-  video_capturer->ChangeTarget(GetFrameSinkId());
+  video_capturer->ChangeTarget(GetFrameSinkId(), viz::SubtreeCaptureId());
   return video_capturer;
 }
 
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 8ede898..ee3bc6c 100644
--- a/services/viz/privileged/mojom/compositing/frame_sink_video_capture.mojom
+++ b/services/viz/privileged/mojom/compositing/frame_sink_video_capture.mojom
@@ -9,6 +9,7 @@
 import "mojo/public/mojom/base/time.mojom";
 import "mojo/public/mojom/base/shared_memory.mojom";
 import "services/viz/public/mojom/compositing/frame_sink_id.mojom";
+import "services/viz/public/mojom/compositing/subtree_capture_id.mojom";
 import "skia/public/mojom/bitmap.mojom";
 import "ui/gfx/geometry/mojom/geometry.mojom";
 import "ui/gfx/mojom/color_space.mojom";
@@ -117,9 +118,13 @@
   SetAutoThrottlingEnabled(bool enabled);
 
   // Targets a different compositor frame sink. This may be called anytime,
-  // before or after Start(). If the argument is null, capture will suspend
-  // until a new frame sink target is set.
-  ChangeTarget(FrameSinkId? frame_sink_id);
+  // before or after Start(). If |frame_sink_id| is null, capture will suspend
+  // until a new frame sink target is set. If the given |subtree_capture_id| is
+  // valid, the capturer will capture a render pass associated with a layer
+  // subtree under the target frame sink, which is identifiable by that
+  // |subtree_capture_id|. Otherwise, the capturer captures the root render pass
+  // of the target frame sink.
+  ChangeTarget(FrameSinkId? frame_sink_id, SubtreeCaptureId subtree_capture_id);
 
   // Starts emitting video frames to the given |consumer|.
   Start(pending_remote<FrameSinkVideoConsumer> consumer);
diff --git a/services/viz/public/cpp/compositing/compositor_render_pass_mojom_traits.cc b/services/viz/public/cpp/compositing/compositor_render_pass_mojom_traits.cc
index 4f939e7..6b428e9 100644
--- a/services/viz/public/cpp/compositing/compositor_render_pass_mojom_traits.cc
+++ b/services/viz/public/cpp/compositing/compositor_render_pass_mojom_traits.cc
@@ -8,6 +8,7 @@
 #include "components/viz/common/quads/compositor_render_pass.h"
 #include "services/viz/public/cpp/compositing/compositor_render_pass_id_mojom_traits.h"
 #include "services/viz/public/cpp/compositing/shared_quad_state_mojom_traits.h"
+#include "services/viz/public/cpp/compositing/subtree_capture_id_mojom_traits.h"
 #include "services/viz/public/cpp/crash_keys.h"
 #include "ui/gfx/mojom/display_color_spaces_mojom_traits.h"
 
@@ -25,6 +26,7 @@
       !data.ReadFilters(&(*out)->filters) ||
       !data.ReadBackdropFilters(&(*out)->backdrop_filters) ||
       !data.ReadBackdropFilterBounds(&(*out)->backdrop_filter_bounds) ||
+      !data.ReadSubtreeCaptureId(&(*out)->subtree_capture_id) ||
       !data.ReadCopyRequests(&(*out)->copy_requests) ||
       !data.ReadId(&(*out)->id)) {
     return false;
diff --git a/services/viz/public/cpp/compositing/compositor_render_pass_mojom_traits.h b/services/viz/public/cpp/compositing/compositor_render_pass_mojom_traits.h
index cf79f2b..cbb5a81 100644
--- a/services/viz/public/cpp/compositing/compositor_render_pass_mojom_traits.h
+++ b/services/viz/public/cpp/compositing/compositor_render_pass_mojom_traits.h
@@ -10,6 +10,7 @@
 
 #include "base/check.h"
 #include "components/viz/common/quads/compositor_render_pass.h"
+#include "components/viz/common/surfaces/subtree_capture_id.h"
 #include "services/viz/public/cpp/compositing/copy_output_request_mojom_traits.h"
 #include "services/viz/public/cpp/compositing/quads_mojom_traits.h"
 #include "services/viz/public/mojom/compositing/compositor_render_pass.mojom-shared.h"
@@ -58,6 +59,11 @@
     return input->backdrop_filter_bounds;
   }
 
+  static viz::SubtreeCaptureId subtree_capture_id(
+      const std::unique_ptr<viz::CompositorRenderPass>& input) {
+    return input->subtree_capture_id;
+  }
+
   static bool has_transparent_background(
       const std::unique_ptr<viz::CompositorRenderPass>& input) {
     return input->has_transparent_background;
diff --git a/services/viz/public/cpp/compositing/mojom_traits_perftest.cc b/services/viz/public/cpp/compositing/mojom_traits_perftest.cc
index a740cc7..b7ba834 100644
--- a/services/viz/public/cpp/compositing/mojom_traits_perftest.cc
+++ b/services/viz/public/cpp/compositing/mojom_traits_perftest.cc
@@ -10,6 +10,7 @@
 #include "components/viz/common/quads/solid_color_draw_quad.h"
 #include "components/viz/common/quads/texture_draw_quad.h"
 #include "components/viz/common/quads/tile_draw_quad.h"
+#include "components/viz/common/surfaces/subtree_capture_id.h"
 #include "components/viz/test/compositor_frame_helpers.h"
 #include "gpu/ipc/common/mailbox_holder_mojom_traits.h"
 #include "gpu/ipc/common/mailbox_mojom_traits.h"
@@ -208,8 +209,8 @@
     auto pass_in = CompositorRenderPass::Create();
     pass_in->SetAll(root_id, arbitrary_rect1, arbitrary_rect2,
                     arbitrary_matrix1, arbitrary_filters2, arbitrary_filters1,
-                    arbitrary_rrectf1, arbitrary_bool1, arbitrary_bool1,
-                    arbitrary_bool1, arbitrary_bool1);
+                    arbitrary_rrectf1, SubtreeCaptureId(), arbitrary_bool1,
+                    arbitrary_bool1, arbitrary_bool1, arbitrary_bool1);
 
     // Texture quads
     for (uint32_t i = 0; i < 10; ++i) {
diff --git a/services/viz/public/cpp/compositing/mojom_traits_unittest.cc b/services/viz/public/cpp/compositing/mojom_traits_unittest.cc
index 76b3558..4393f48 100644
--- a/services/viz/public/cpp/compositing/mojom_traits_unittest.cc
+++ b/services/viz/public/cpp/compositing/mojom_traits_unittest.cc
@@ -17,6 +17,7 @@
 #include "components/viz/common/resources/resource_settings.h"
 #include "components/viz/common/resources/returned_resource.h"
 #include "components/viz/common/resources/transferable_resource.h"
+#include "components/viz/common/surfaces/subtree_capture_id.h"
 #include "components/viz/common/surfaces/surface_info.h"
 #include "components/viz/common/surfaces/surface_range.h"
 #include "components/viz/test/begin_frame_args_test.h"
@@ -704,6 +705,7 @@
   backdrop_filters.Append(cc::FilterOperation::CreateSaturateFilter(2.f));
   base::Optional<gfx::RRectF> backdrop_filter_bounds(
       {10, 20, 130, 140, 1, 2, 3, 4, 5, 6, 7, 8});
+  SubtreeCaptureId subtree_capture_id{22u};
   const bool has_transparent_background = true;
   const bool cache_render_pass = true;
   const bool has_damage_from_contributing_content = true;
@@ -711,8 +713,9 @@
   auto input = CompositorRenderPass::Create();
   input->SetAll(render_pass_id, output_rect, damage_rect, transform_to_root,
                 filters, backdrop_filters, backdrop_filter_bounds,
-                has_transparent_background, cache_render_pass,
-                has_damage_from_contributing_content, generate_mipmap);
+                subtree_capture_id, has_transparent_background,
+                cache_render_pass, has_damage_from_contributing_content,
+                generate_mipmap);
   input->copy_requests.push_back(CopyOutputRequest::CreateStubForTesting());
   const gfx::Rect copy_output_area(24, 42, 75, 57);
   input->copy_requests.back()->set_area(copy_output_area);
@@ -778,6 +781,7 @@
   EXPECT_EQ(filters, output->filters);
   EXPECT_EQ(backdrop_filters, output->backdrop_filters);
   EXPECT_EQ(backdrop_filter_bounds, output->backdrop_filter_bounds);
+  EXPECT_EQ(subtree_capture_id, output->subtree_capture_id);
   EXPECT_EQ(cache_render_pass, output->cache_render_pass);
   EXPECT_EQ(has_damage_from_contributing_content,
             output->has_damage_from_contributing_content);
@@ -849,6 +853,7 @@
   const gfx::Transform transform_to_root =
       gfx::Transform(1.0, 0.5, 0.5, -0.5, -1.0, 0.0);
   const base::Optional<gfx::RRectF> backdrop_filter_bounds;
+  SubtreeCaptureId subtree_capture_id;
   const bool has_transparent_background = true;
   const bool cache_render_pass = false;
   const bool has_damage_from_contributing_content = false;
@@ -856,9 +861,9 @@
   auto input = CompositorRenderPass::Create();
   input->SetAll(render_pass_id, output_rect, damage_rect, transform_to_root,
                 cc::FilterOperations(), cc::FilterOperations(),
-                backdrop_filter_bounds, has_transparent_background,
-                cache_render_pass, has_damage_from_contributing_content,
-                generate_mipmap);
+                backdrop_filter_bounds, subtree_capture_id,
+                has_transparent_background, cache_render_pass,
+                has_damage_from_contributing_content, generate_mipmap);
 
   // Unlike the previous test, don't add any quads to the list; we need to
   // verify that the serialization code can deal with that.
@@ -874,6 +879,8 @@
   EXPECT_EQ(damage_rect, output->damage_rect);
   EXPECT_EQ(transform_to_root, output->transform_to_root_target);
   EXPECT_EQ(backdrop_filter_bounds, output->backdrop_filter_bounds);
+  EXPECT_EQ(subtree_capture_id, output->subtree_capture_id);
+  EXPECT_FALSE(output->subtree_capture_id.is_valid());
   EXPECT_EQ(has_transparent_background, output->has_transparent_background);
 }
 
diff --git a/services/viz/public/cpp/compositing/subtree_capture_id_mojom_traits.h b/services/viz/public/cpp/compositing/subtree_capture_id_mojom_traits.h
new file mode 100644
index 0000000..a9f44c7
--- /dev/null
+++ b/services/viz/public/cpp/compositing/subtree_capture_id_mojom_traits.h
@@ -0,0 +1,29 @@
+// Copyright 2020 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_SUBTREE_CAPTURE_ID_MOJOM_TRAITS_H_
+#define SERVICES_VIZ_PUBLIC_CPP_COMPOSITING_SUBTREE_CAPTURE_ID_MOJOM_TRAITS_H_
+
+#include "components/viz/common/surfaces/subtree_capture_id.h"
+#include "services/viz/public/mojom/compositing/subtree_capture_id.mojom-shared.h"
+
+namespace mojo {
+
+template <>
+struct StructTraits<viz::mojom::SubtreeCaptureIdDataView,
+                    viz::SubtreeCaptureId> {
+  static uint32_t subtree_id(const viz::SubtreeCaptureId& subtree_capture_id) {
+    return subtree_capture_id.subtree_id();
+  }
+
+  static bool Read(viz::mojom::SubtreeCaptureIdDataView data,
+                   viz::SubtreeCaptureId* out) {
+    *out = viz::SubtreeCaptureId(data.subtree_id());
+    return true;
+  }
+};
+
+}  // namespace mojo
+
+#endif  // SERVICES_VIZ_PUBLIC_CPP_COMPOSITING_SUBTREE_CAPTURE_ID_MOJOM_TRAITS_H_
diff --git a/services/viz/public/mojom/BUILD.gn b/services/viz/public/mojom/BUILD.gn
index 3dc726d..2d20b18 100644
--- a/services/viz/public/mojom/BUILD.gn
+++ b/services/viz/public/mojom/BUILD.gn
@@ -32,6 +32,7 @@
     "compositing/returned_resource.mojom",
     "compositing/selection.mojom",
     "compositing/shared_quad_state.mojom",
+    "compositing/subtree_capture_id.mojom",
     "compositing/surface_id.mojom",
     "compositing/surface_info.mojom",
     "compositing/surface_range.mojom",
@@ -109,6 +110,16 @@
     {
       types = [
         {
+          mojom = "viz.mojom.SubtreeCaptureId"
+          cpp = "::viz::SubtreeCaptureId"
+        },
+      ]
+      traits_headers = [ "//services/viz/public/cpp/compositing/subtree_capture_id_mojom_traits.h" ]
+      traits_public_deps = [ "//components/viz/common" ]
+    },
+    {
+      types = [
+        {
           mojom = "viz.mojom.SurfaceId"
           cpp = "::viz::SurfaceId"
         },
diff --git a/services/viz/public/mojom/compositing/compositor_render_pass.mojom b/services/viz/public/mojom/compositing/compositor_render_pass.mojom
index be3cfa7..1db67c6 100644
--- a/services/viz/public/mojom/compositing/compositor_render_pass.mojom
+++ b/services/viz/public/mojom/compositing/compositor_render_pass.mojom
@@ -8,6 +8,7 @@
 import "services/viz/public/mojom/compositing/copy_output_request.mojom";
 import "services/viz/public/mojom/compositing/filter_operations.mojom";
 import "services/viz/public/mojom/compositing/quads.mojom";
+import "services/viz/public/mojom/compositing/subtree_capture_id.mojom";
 import "ui/gfx/geometry/mojom/geometry.mojom";
 import "ui/gfx/mojom/rrect_f.mojom";
 import "ui/gfx/mojom/transform.mojom";
@@ -21,6 +22,7 @@
   FilterOperations filters;
   FilterOperations backdrop_filters;
   gfx.mojom.RRectF? backdrop_filter_bounds;
+  SubtreeCaptureId subtree_capture_id;
   bool has_transparent_background;
   bool cache_render_pass = false;
   bool has_damage_from_contributing_content = false;
diff --git a/services/viz/public/mojom/compositing/subtree_capture_id.mojom b/services/viz/public/mojom/compositing/subtree_capture_id.mojom
new file mode 100644
index 0000000..691c6080
--- /dev/null
+++ b/services/viz/public/mojom/compositing/subtree_capture_id.mojom
@@ -0,0 +1,10 @@
+// Copyright 2020 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.
+
+module viz.mojom;
+
+// See SubtreeCaptureId in components/viz/common/surfaces/subtree_capture_id.h.
+struct SubtreeCaptureId {
+  uint32 subtree_id;
+};
diff --git a/ui/aura/BUILD.gn b/ui/aura/BUILD.gn
index 94a749246..22d0ec9 100644
--- a/ui/aura/BUILD.gn
+++ b/ui/aura/BUILD.gn
@@ -37,6 +37,7 @@
     "scoped_enable_unadjusted_mouse_events.h",
     "scoped_keyboard_hook.h",
     "scoped_simple_keyboard_hook.h",
+    "scoped_window_capture_request.h",
     "scoped_window_event_targeting_blocker.h",
     "scoped_window_targeter.h",
     "window.h",
@@ -79,6 +80,7 @@
     "null_window_targeter.cc",
     "scoped_keyboard_hook.cc",
     "scoped_simple_keyboard_hook.cc",
+    "scoped_window_capture_request.cc",
     "scoped_window_event_targeting_blocker.cc",
     "scoped_window_targeter.cc",
     "window.cc",
diff --git a/ui/aura/scoped_window_capture_request.cc b/ui/aura/scoped_window_capture_request.cc
new file mode 100644
index 0000000..0d7c3f2
--- /dev/null
+++ b/ui/aura/scoped_window_capture_request.cc
@@ -0,0 +1,42 @@
+// Copyright 2020 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 "ui/aura/scoped_window_capture_request.h"
+
+#include "ui/aura/window.h"
+
+namespace aura {
+
+ScopedWindowCaptureRequest::ScopedWindowCaptureRequest(
+    ScopedWindowCaptureRequest&& other)
+    : window_(other.window_) {
+  other.window_ = nullptr;
+}
+
+ScopedWindowCaptureRequest& ScopedWindowCaptureRequest::operator=(
+    ScopedWindowCaptureRequest&& rhs) {
+  if (window_)
+    window_->OnScopedWindowCaptureRequestRemoved();
+  window_ = rhs.window_;
+  rhs.window_ = nullptr;
+  return *this;
+}
+
+ScopedWindowCaptureRequest::~ScopedWindowCaptureRequest() {
+  if (window_)
+    window_->OnScopedWindowCaptureRequestRemoved();
+}
+
+viz::SubtreeCaptureId ScopedWindowCaptureRequest::GetCaptureId() const {
+  return window_ ? window_->subtree_capture_id() : viz::SubtreeCaptureId();
+}
+
+ScopedWindowCaptureRequest::ScopedWindowCaptureRequest(Window* window)
+    : window_(window) {
+  DCHECK(window_);
+  DCHECK(!window_->IsRootWindow());
+  window_->OnScopedWindowCaptureRequestAdded();
+}
+
+}  // namespace aura
diff --git a/ui/aura/scoped_window_capture_request.h b/ui/aura/scoped_window_capture_request.h
new file mode 100644
index 0000000..ee7e4f95
--- /dev/null
+++ b/ui/aura/scoped_window_capture_request.h
@@ -0,0 +1,50 @@
+// Copyright 2020 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 UI_AURA_SCOPED_WINDOW_CAPTURE_REQUEST_H_
+#define UI_AURA_SCOPED_WINDOW_CAPTURE_REQUEST_H_
+
+#include "components/viz/common/surfaces/subtree_capture_id.h"
+#include "ui/aura/aura_export.h"
+
+namespace aura {
+
+class Window;
+
+// A scoped move-only object which is associated with a request to make a
+// non-root window individually capturable by a FrameSinkVideoCapturer. This
+// request is tracked as long as this object lives. Once all requests are
+// destroyed, the window will no longer be uniquely identifiable by a
+// viz::SubtreeCaptureId, and can no longer be individually capturable by the
+// FrameSinkVideoCapturer.
+// Note that making a window capturable forces the layer tree root at its layer
+// to be promoted to a render surface that draw into a render pass.
+// See https://crbug.com/1143930 for more details.
+class AURA_EXPORT ScopedWindowCaptureRequest {
+ public:
+  // Creates an empty request that doesn't affect any window.
+  ScopedWindowCaptureRequest() = default;
+  ScopedWindowCaptureRequest(ScopedWindowCaptureRequest&& other);
+  ScopedWindowCaptureRequest& operator=(ScopedWindowCaptureRequest&& rhs);
+  ~ScopedWindowCaptureRequest();
+
+  Window* window() const { return window_; }
+
+  viz::SubtreeCaptureId GetCaptureId() const;
+
+ private:
+  friend class Window;
+
+  // Private so it can only be called through Window::MakeWindowCapturable().
+  explicit ScopedWindowCaptureRequest(Window* window);
+
+  // The window on which this request has been made. Can be |nullptr| if this is
+  // an empty request (created by the default ctor), or if this object was
+  // std::move()'d from.
+  Window* window_ = nullptr;
+};
+
+}  // namespace aura
+
+#endif  // UI_AURA_SCOPED_WINDOW_CAPTURE_REQUEST_H_
diff --git a/ui/aura/window.cc b/ui/aura/window.cc
index 5fa9986..413d676 100644
--- a/ui/aura/window.cc
+++ b/ui/aura/window.cc
@@ -283,6 +283,13 @@
   return visible_ ? layer()->IsDrawn() : false;
 }
 
+ScopedWindowCaptureRequest Window::MakeWindowCapturable() {
+  DCHECK(!IsRootWindow()) << "Root windows can already be captured using their "
+                             "FrameSinkId; no need to call this.";
+
+  return ScopedWindowCaptureRequest(this);
+}
+
 gfx::Rect Window::GetBoundsInRootWindow() const {
   // TODO(beng): There may be a better way to handle this, and the existing code
   //             is likely wrong anyway in a multi-display world, but this will
@@ -1413,6 +1420,18 @@
   return screen_location;
 }
 
+std::unique_ptr<ui::Layer> Window::ReleaseLayer() {
+  if (number_of_capture_requests_) {
+    // Before we release our own layer, if this window was marked for capture,
+    // we need to reset the SubtreeCaptureId on that layer as it will no longer
+    // be associated with us.
+    DCHECK(subtree_capture_id_.is_valid());
+    if (layer())
+      layer()->SetSubtreeCaptureId(viz::SubtreeCaptureId());
+  }
+  return LayerOwner::ReleaseLayer();
+}
+
 std::unique_ptr<ui::Layer> Window::RecreateLayer() {
   WindowOcclusionTracker::ScopedPause pause_occlusion_tracking;
 
@@ -1430,6 +1449,12 @@
   if (GetFrameSinkId().is_valid() && old_layer)
     AllocateLocalSurfaceId();
 
+  // The old layer subtree must no longer be capturable.
+  // Note that we don't need to worry about the newly recreated layer since
+  // Window::SetLayer() will have taken care of it already.
+  if (number_of_capture_requests_ && old_layer)
+    old_layer->SetSubtreeCaptureId(viz::SubtreeCaptureId());
+
   // Observers are guaranteed to be notified when an opacity or transform
   // animation ends.
   if (was_animating_opacity) {
@@ -1451,6 +1476,17 @@
   return old_layer;
 }
 
+void Window::SetLayer(std::unique_ptr<ui::Layer> alayer) {
+  LayerOwner::SetLayer(std::move(alayer));
+  if (number_of_capture_requests_) {
+    // If this window was marked for capture before, then the new layer that we
+    // own now should be given the current SubtreeCaptureId that we have.
+    DCHECK(subtree_capture_id_.is_valid());
+    if (layer())
+      layer()->SetSubtreeCaptureId(subtree_capture_id_);
+  }
+}
+
 void Window::OnFirstSurfaceActivation(const viz::SurfaceInfo& surface_info) {
   DCHECK_EQ(surface_info.id().frame_sink_id(), GetFrameSinkId());
   layer()->SetShowSurface(surface_info.id(), bounds().size(), SK_ColorWHITE,
@@ -1508,4 +1544,32 @@
   return parent_local_surface_id_allocator_.get() != nullptr;
 }
 
+void Window::OnScopedWindowCaptureRequestAdded() {
+  if (++number_of_capture_requests_ == 1) {
+    DCHECK(!subtree_capture_id_.is_valid());
+    DCHECK(!layer() || !layer()->GetSubtreeCaptureId().is_valid());
+
+    subtree_capture_id_ =
+        Env::GetInstance()->context_factory()->AllocateSubtreeCaptureId();
+    if (layer())
+      layer()->SetSubtreeCaptureId(subtree_capture_id_);
+  }
+
+  DCHECK(subtree_capture_id_.is_valid());
+}
+
+void Window::OnScopedWindowCaptureRequestRemoved() {
+  DCHECK(subtree_capture_id_.is_valid());
+  DCHECK(number_of_capture_requests_);
+
+  --number_of_capture_requests_;
+  DCHECK_GE(number_of_capture_requests_, 0);
+
+  if (number_of_capture_requests_ == 0) {
+    subtree_capture_id_ = viz::SubtreeCaptureId();
+    if (layer())
+      layer()->SetSubtreeCaptureId(subtree_capture_id_);
+  }
+}
+
 }  // namespace aura
diff --git a/ui/aura/window.h b/ui/aura/window.h
index 2dfa559..ca46ecf 100644
--- a/ui/aura/window.h
+++ b/ui/aura/window.h
@@ -26,9 +26,11 @@
 #include "components/viz/common/surfaces/frame_sink_id.h"
 #include "components/viz/common/surfaces/local_surface_id.h"
 #include "components/viz/common/surfaces/scoped_surface_id_allocator.h"
+#include "components/viz/common/surfaces/subtree_capture_id.h"
 #include "components/viz/host/host_frame_sink_client.h"
 #include "ui/aura/aura_export.h"
 #include "ui/aura/client/window_types.h"
+#include "ui/aura/scoped_window_capture_request.h"
 #include "ui/aura/window_observer.h"
 #include "ui/base/class_property.h"
 #include "ui/compositor/layer_animator.h"
@@ -233,6 +235,28 @@
     return occluded_region_in_root_;
   }
 
+  // Makes this *non-root* window individually capturable by the
+  // |FrameSinkVideoCapturer| by tagging its layer with a unique
+  // |viz::SubtreeCaptureId| which will force the layer tree root at this
+  // window's layer to a render surface that draws into a render pass that is
+  // identifiable by the capturer using that ID.
+  //
+  // Note that this should only be called for non-root windows. Root windows are
+  // already capturable by the capturer as they're identifiable by their
+  // |viz::FrameSinkId| and thei associated root render pass, so there's no need
+  // to call this.
+  //
+  // This returns a scoped object associated with this request to make the
+  // window capturable, since multiple capturers can capture the same window at
+  // the same time. Once all requests are destroyed, this window will no longer
+  // be individually capturable, and its layer won't be tagged with a valid
+  // |viz::SubtreeCaptureId|.
+  // See https://crbug.com/1143930 for more details.
+  ScopedWindowCaptureRequest MakeWindowCapturable() WARN_UNUSED_RESULT;
+  const viz::SubtreeCaptureId& subtree_capture_id() const {
+    return subtree_capture_id_;
+  }
+
   // Returns the window's bounds in root window's coordinates.
   gfx::Rect GetBoundsInRootWindow() const;
 
@@ -400,8 +424,10 @@
                                   float new_device_scale_factor) override;
   void UpdateVisualState() override;
 
-  // Overridden from ui::LayerOwner:
+  // ui::LayerOwner:
+  std::unique_ptr<ui::Layer> ReleaseLayer() override;
   std::unique_ptr<ui::Layer> RecreateLayer() override;
+  void SetLayer(std::unique_ptr<ui::Layer> layer) override;
 
 #if DCHECK_IS_ON()
   // These methods are useful when debugging.
@@ -508,6 +534,7 @@
   friend class HitTestDataProviderAura;
   friend class LayoutManager;
   friend class PropertyConverter;
+  friend class ScopedWindowCaptureRequest;
   friend class ScopedWindowEventTargetingBlocker;
   friend class WindowTargeter;
   friend class test::WindowTestApi;
@@ -628,6 +655,14 @@
   const viz::LocalSurfaceId& GetCurrentLocalSurfaceId() const;
   bool IsEmbeddingExternalContent() const;
 
+  // Called by the constructor of ScopedWindowCaptureRequest to add a request to
+  // make this non-root window capturable by the FrameSinkVideoCapturer.
+  void OnScopedWindowCaptureRequestAdded();
+
+  // Called by the destructor of ScopedWindowCaptureRequest to remove a request
+  // to make this non-root window capturable by the FrameSinkVideoCapturer.
+  void OnScopedWindowCaptureRequestRemoved();
+
   // Bounds of this window relative to the parent. This is cached as the bounds
   // of the Layer and Window are not necessarily the same. In particular bounds
   // of the Layer are relative to the first ancestor with a Layer, where as this
@@ -688,6 +723,22 @@
 
   base::ReentrantObserverList<WindowObserver, true> observers_;
 
+  // Video capturing support ---------------------------------------------------
+
+  // A non-root window must be marked with a viz::SubtreeCaptureId so that it
+  // can be captured by a FrameSinkVideoCapturer. Multiple clients can request
+  // to capture the same window at the same time. This is the number of those
+  // requests, which once it goes to zero, we well clear the
+  // viz::SubtreeCaptureId from the layer associated with this window.
+  int number_of_capture_requests_ = 0;
+
+  // The ID allocated for the layer tree rooted at this window's layer, so that
+  // it can be uniquely identified by the FrameSinkVideoCapturer. This can only
+  // be set for non-root windows. Root windows can be captured normally by the
+  // capturer using their frame sink ID, since those root windows are already
+  // associated with a root compositor render pass.
+  viz::SubtreeCaptureId subtree_capture_id_;
+
   // Embedding support ---------------------------------------------------------
 
   // Used to detect changes in device scale factor that require generating a
diff --git a/ui/aura/window_unittest.cc b/ui/aura/window_unittest.cc
index 6bf4c71..a30c5ee1 100644
--- a/ui/aura/window_unittest.cc
+++ b/ui/aura/window_unittest.cc
@@ -6,6 +6,7 @@
 
 #include <limits.h>
 
+#include <memory>
 #include <string>
 #include <utility>
 #include <vector>
@@ -24,6 +25,7 @@
 #include "ui/aura/client/visibility_client.h"
 #include "ui/aura/client/window_parenting_client.h"
 #include "ui/aura/layout_manager.h"
+#include "ui/aura/scoped_window_capture_request.h"
 #include "ui/aura/scoped_window_event_targeting_blocker.h"
 #include "ui/aura/test/aura_test_base.h"
 #include "ui/aura/test/aura_test_utils.h"
@@ -368,6 +370,92 @@
   EXPECT_FALSE(w->ContainsPoint(gfx::Point(10, 10)));
 }
 
+TEST_F(WindowTest, MakeWindowCapturable) {
+  std::unique_ptr<Window> w1(CreateTestWindowWithId(1, root_window()));
+  // Initailly the window is not capturable.
+  EXPECT_FALSE(w1->subtree_capture_id().is_valid());
+
+  // Creating requests makes the window capturable as long as those requests
+  // remain alive.
+  ScopedWindowCaptureRequest request1 = w1->MakeWindowCapturable();
+  EXPECT_TRUE(w1->subtree_capture_id().is_valid());
+  EXPECT_EQ(request1.GetCaptureId(), w1->subtree_capture_id());
+  EXPECT_EQ(request1.GetCaptureId(), w1->layer()->GetSubtreeCaptureId());
+
+  // A new request does not affect the subtree capture ID.
+  const viz::SubtreeCaptureId current_id = w1->subtree_capture_id();
+  ScopedWindowCaptureRequest request2 = w1->MakeWindowCapturable();
+  EXPECT_EQ(current_id, w1->subtree_capture_id());
+  EXPECT_EQ(request1.GetCaptureId(), request2.GetCaptureId());
+
+  // Create a new request, then move an existing request into it. This should
+  // invalidate the moved-from request.
+  ScopedWindowCaptureRequest request3 = w1->MakeWindowCapturable();
+  EXPECT_EQ(current_id, request3.GetCaptureId());
+  request3 = std::move(request2);
+  EXPECT_FALSE(request2.window());
+  EXPECT_FALSE(request2.GetCaptureId().is_valid());
+  EXPECT_TRUE(w1->subtree_capture_id().is_valid());
+
+  // Destroying |request2| does nothing.
+  auto consume_request = [](ScopedWindowCaptureRequest request) {};
+  consume_request(std::move(request2));
+  EXPECT_TRUE(w1->subtree_capture_id().is_valid());
+  EXPECT_EQ(current_id, w1->subtree_capture_id());
+
+  // Destroying |request1| won't affect the window, it will remain capturable,
+  // since |request3| is still alive.
+  consume_request(std::move(request1));
+  EXPECT_FALSE(request1.window());
+  EXPECT_FALSE(request1.GetCaptureId().is_valid());
+  EXPECT_TRUE(w1->subtree_capture_id().is_valid());
+  EXPECT_EQ(current_id, w1->subtree_capture_id());
+
+  // Once all requests are destroyed, the window no longer has a valid capture
+  // ID.
+  consume_request(std::move(request3));
+  EXPECT_FALSE(w1->subtree_capture_id().is_valid());
+  EXPECT_FALSE(w1->layer()->GetSubtreeCaptureId().is_valid());
+}
+
+TEST_F(WindowTest, LayerReleasingAndSettingOfCapturableWindow) {
+  std::unique_ptr<Window> w1(CreateTestWindowWithId(1, root_window()));
+  EXPECT_FALSE(w1->subtree_capture_id().is_valid());
+  ScopedWindowCaptureRequest request = w1->MakeWindowCapturable();
+  EXPECT_TRUE(w1->layer()->GetSubtreeCaptureId().is_valid());
+
+  // Releasing the capturable window's layer (i.e. it's no longer associated
+  // with the window) will clear its capture ID. However, the window remains
+  // marked as capturable with a valid SubtreeCaptureId even though it has no
+  // layer.
+  std::unique_ptr<ui::Layer> taken_layer = w1->ReleaseLayer();
+  EXPECT_FALSE(w1->layer());
+  EXPECT_FALSE(taken_layer->GetSubtreeCaptureId().is_valid());
+  EXPECT_TRUE(w1->subtree_capture_id().is_valid());
+
+  // Setting a new layer on the window will set the layer's capture ID.
+  auto new_layer = std::make_unique<ui::Layer>();
+  taken_layer->parent()->Add(new_layer.get());
+  w1->Reset(std::move(new_layer));
+  EXPECT_TRUE(w1->layer()->GetSubtreeCaptureId().is_valid());
+  EXPECT_EQ(request.GetCaptureId(), w1->layer()->GetSubtreeCaptureId());
+}
+
+TEST_F(WindowTest, RecreateLayerOfCapturableWindow) {
+  std::unique_ptr<Window> w1(CreateTestWindowWithId(1, root_window()));
+  EXPECT_FALSE(w1->subtree_capture_id().is_valid());
+  ScopedWindowCaptureRequest request = w1->MakeWindowCapturable();
+  EXPECT_TRUE(w1->layer()->GetSubtreeCaptureId().is_valid());
+
+  // Recreating the layer of a capturable window will preserve the capture ID
+  // on the newly recreated window, and clears it from the old layer.
+  const viz::SubtreeCaptureId current_id = w1->subtree_capture_id();
+  std::unique_ptr<ui::Layer> old_layer = w1->RecreateLayer();
+  EXPECT_FALSE(old_layer->GetSubtreeCaptureId().is_valid());
+  EXPECT_EQ(current_id, w1->subtree_capture_id());
+  EXPECT_EQ(current_id, w1->layer()->GetSubtreeCaptureId());
+}
+
 TEST_F(WindowTest, ConvertPointToWindow) {
   // Window::ConvertPointToWindow is mostly identical to
   // Layer::ConvertPointToLayer, except NULL values for |source| are permitted,
diff --git a/ui/compositor/compositor.h b/ui/compositor/compositor.h
index d87ee0d..e1db8f7 100644
--- a/ui/compositor/compositor.h
+++ b/ui/compositor/compositor.h
@@ -26,6 +26,7 @@
 #include "cc/trees/layer_tree_host_single_thread_client.h"
 #include "components/viz/common/frame_sinks/begin_frame_args.h"
 #include "components/viz/common/surfaces/frame_sink_id.h"
+#include "components/viz/common/surfaces/subtree_capture_id.h"
 #include "components/viz/host/host_frame_sink_client.h"
 #include "mojo/public/cpp/bindings/pending_remote.h"
 #include "services/viz/privileged/mojom/compositing/vsync_parameter_observer.mojom-forward.h"
@@ -125,6 +126,9 @@
   // Allocate a new client ID for the display compositor.
   virtual viz::FrameSinkId AllocateFrameSinkId() = 0;
 
+  // Allocates a new capture ID for a layer subtree within a frame sink.
+  virtual viz::SubtreeCaptureId AllocateSubtreeCaptureId() = 0;
+
   // Gets the frame sink manager host instance.
   virtual viz::HostFrameSinkManager* GetHostFrameSinkManager() = 0;
 };
diff --git a/ui/compositor/layer.cc b/ui/compositor/layer.cc
index 5f5eecd..fb7c472 100644
--- a/ui/compositor/layer.cc
+++ b/ui/compositor/layer.cc
@@ -459,6 +459,14 @@
   return animator_.get();
 }
 
+void Layer::SetSubtreeCaptureId(viz::SubtreeCaptureId subtree_id) {
+  cc_layer_->SetSubtreeCaptureId(subtree_id);
+}
+
+viz::SubtreeCaptureId Layer::GetSubtreeCaptureId() const {
+  return cc_layer_->subtree_capture_id();
+}
+
 void Layer::SetTransform(const gfx::Transform& transform) {
   GetAnimator()->SetTransform(transform);
 }
@@ -781,6 +789,7 @@
   new_layer->SetBackgroundColor(cc_layer_->background_color());
   new_layer->SetSafeOpaqueBackgroundColor(
       cc_layer_->SafeOpaqueBackgroundColor());
+  new_layer->SetSubtreeCaptureId(cc_layer_->subtree_capture_id());
   new_layer->SetCacheRenderSurface(cc_layer_->cache_render_surface());
   new_layer->SetTrilinearFiltering(cc_layer_->trilinear_filtering());
   new_layer->SetRoundedCorner(cc_layer_->corner_radii());
diff --git a/ui/compositor/layer.h b/ui/compositor/layer.h
index f2a863d..e82ad7e 100644
--- a/ui/compositor/layer.h
+++ b/ui/compositor/layer.h
@@ -23,6 +23,7 @@
 #include "cc/layers/surface_layer.h"
 #include "cc/layers/texture_layer_client.h"
 #include "components/viz/common/resources/transferable_resource.h"
+#include "components/viz/common/surfaces/subtree_capture_id.h"
 #include "third_party/skia/include/core/SkColor.h"
 #include "ui/compositor/compositor.h"
 #include "ui/compositor/layer_animation_delegate.h"
@@ -169,6 +170,23 @@
   // been set. Will not return NULL.
   LayerAnimator* GetAnimator();
 
+  // Sets the given |subtree_id| on the cc::Layer associated with this, so that
+  // the layer subtree rooted here can be uniquely identified by a
+  // FrameSinkVideoCapturer. The existence of a valid SubtreeCaptureId on this
+  // layer will force it to be drawn into a separate CompositorRenderPass.
+  // Setting a non-valid (i.e. default-constructed SubtreeCaptureId) will clear
+  // this property.
+  // It is not allowed to change this ID from a valid ID to another valid ID,
+  // since a client might already using the existing valid ID to make this layer
+  // subtree identifiable by a capturer.
+  //
+  // Note that this is useful when it's desired to video record a layer subtree
+  // of a non-root layer using a FrameSinkVideoCapturer, since non-root layers
+  // are usually not drawn into their own CompositorRenderPass, while the ui
+  // compositor's root layer always is.
+  void SetSubtreeCaptureId(viz::SubtreeCaptureId subtree_id);
+  viz::SubtreeCaptureId GetSubtreeCaptureId() const;
+
   // The transform, relative to the parent.
   void SetTransform(const gfx::Transform& transform);
   const gfx::Transform& transform() const { return cc_layer_->transform(); }
diff --git a/ui/compositor/layer_owner.h b/ui/compositor/layer_owner.h
index c541ca9..f3ea42e7 100644
--- a/ui/compositor/layer_owner.h
+++ b/ui/compositor/layer_owner.h
@@ -44,7 +44,7 @@
   std::unique_ptr<Layer> AcquireLayer();
 
   // Similar to AcquireLayer(), but layer() will be set to nullptr immediately.
-  std::unique_ptr<Layer> ReleaseLayer();
+  virtual std::unique_ptr<Layer> ReleaseLayer();
 
   // Releases the ownership of the current layer, and takes ownership of
   // |layer|.
@@ -63,7 +63,7 @@
   bool OwnsLayer() const;
 
  protected:
-  void SetLayer(std::unique_ptr<Layer> layer);
+  virtual void SetLayer(std::unique_ptr<Layer> layer);
   void DestroyLayer();
 
  private:
diff --git a/ui/compositor/layer_unittest.cc b/ui/compositor/layer_unittest.cc
index b4db7e4..9806e412 100644
--- a/ui/compositor/layer_unittest.cc
+++ b/ui/compositor/layer_unittest.cc
@@ -39,6 +39,7 @@
 #include "components/viz/common/frame_sinks/copy_output_result.h"
 #include "components/viz/common/resources/transferable_resource.h"
 #include "components/viz/common/surfaces/parent_local_surface_id_allocator.h"
+#include "components/viz/common/surfaces/subtree_capture_id.h"
 #include "components/viz/common/surfaces/surface_id.h"
 #include "testing/gmock/include/gmock/gmock.h"
 #include "testing/gtest/include/gtest/gtest.h"
@@ -755,6 +756,7 @@
   layer->SetClipRect(clip_rect);
   layer->SetRoundedCornerRadius({1, 2, 4, 5});
   layer->SetIsFastRoundedCorner(true);
+  layer->SetSubtreeCaptureId(viz::SubtreeCaptureId(1));
 
   auto clone = layer->Clone();
 
@@ -773,6 +775,10 @@
   EXPECT_EQ(layer->rounded_corner_radii(), clone->rounded_corner_radii());
   EXPECT_EQ(layer->is_fast_rounded_corner(), clone->is_fast_rounded_corner());
 
+  // However, the SubtreeCaptureId is not cloned.
+  EXPECT_TRUE(layer->GetSubtreeCaptureId().is_valid());
+  EXPECT_FALSE(clone->GetSubtreeCaptureId().is_valid());
+
   layer->SetTransform(gfx::Transform());
   layer->SetColor(SK_ColorGREEN);
   layer->SetLayerInverted(false);
@@ -1018,6 +1024,8 @@
   constexpr gfx::RoundedCornersF kCornerRadii(1, 2, 3, 4);
   l1->SetRoundedCornerRadius(kCornerRadii);
   l1->SetIsFastRoundedCorner(true);
+  constexpr viz::SubtreeCaptureId kSubtreeCaptureId(22);
+  l1->SetSubtreeCaptureId(kSubtreeCaptureId);
 
   EXPECT_EQ(gfx::Point3F(), l1->cc_layer_for_testing()->transform_origin());
   EXPECT_TRUE(l1->cc_layer_for_testing()->DrawsContent());
@@ -1027,6 +1035,9 @@
   EXPECT_TRUE(l1->cc_layer_for_testing()->HasRoundedCorner());
   EXPECT_EQ(l1->cc_layer_for_testing()->corner_radii(), kCornerRadii);
   EXPECT_TRUE(l1->cc_layer_for_testing()->is_fast_rounded_corner());
+  EXPECT_EQ(kSubtreeCaptureId,
+            l1->cc_layer_for_testing()->subtree_capture_id());
+  EXPECT_EQ(kSubtreeCaptureId, l1->GetSubtreeCaptureId());
 
   cc::Layer* before_layer = l1->cc_layer_for_testing();
 
@@ -1050,6 +1061,9 @@
   EXPECT_TRUE(l1->cc_layer_for_testing()->HasRoundedCorner());
   EXPECT_EQ(l1->cc_layer_for_testing()->corner_radii(), kCornerRadii);
   EXPECT_TRUE(l1->cc_layer_for_testing()->is_fast_rounded_corner());
+  EXPECT_EQ(kSubtreeCaptureId,
+            l1->cc_layer_for_testing()->subtree_capture_id());
+  EXPECT_EQ(kSubtreeCaptureId, l1->GetSubtreeCaptureId());
   EXPECT_FALSE(callback1_run);
 
   bool callback2_run = false;
diff --git a/ui/compositor/test/in_process_context_factory.cc b/ui/compositor/test/in_process_context_factory.cc
index 033f94d8..ea06f14 100644
--- a/ui/compositor/test/in_process_context_factory.cc
+++ b/ui/compositor/test/in_process_context_factory.cc
@@ -437,6 +437,10 @@
   return frame_sink_id_allocator_.NextFrameSinkId();
 }
 
+viz::SubtreeCaptureId InProcessContextFactory::AllocateSubtreeCaptureId() {
+  return subtree_capture_id_allocator_.NextSubtreeCaptureId();
+}
+
 viz::HostFrameSinkManager* InProcessContextFactory::GetHostFrameSinkManager() {
   return host_frame_sink_manager_;
 }
diff --git a/ui/compositor/test/in_process_context_factory.h b/ui/compositor/test/in_process_context_factory.h
index 1e8e1c2e..98de309 100644
--- a/ui/compositor/test/in_process_context_factory.h
+++ b/ui/compositor/test/in_process_context_factory.h
@@ -12,6 +12,7 @@
 #include "base/macros.h"
 #include "cc/test/test_task_graph_runner.h"
 #include "components/viz/common/surfaces/frame_sink_id_allocator.h"
+#include "components/viz/common/surfaces/subtree_capture_id_allocator.h"
 #include "components/viz/service/display/display.h"
 #include "components/viz/service/frame_sinks/frame_sink_manager_impl.h"
 #include "components/viz/test/test_gpu_memory_buffer_manager.h"
@@ -69,6 +70,7 @@
   gpu::GpuMemoryBufferManager* GetGpuMemoryBufferManager() override;
   cc::TaskGraphRunner* GetTaskGraphRunner() override;
   viz::FrameSinkId AllocateFrameSinkId() override;
+  viz::SubtreeCaptureId AllocateSubtreeCaptureId() override;
   viz::HostFrameSinkManager* GetHostFrameSinkManager() override;
 
   SkMatrix44 GetOutputColorMatrix(Compositor* compositor) const;
@@ -90,6 +92,7 @@
   viz::TestImageFactory image_factory_;
   cc::TestTaskGraphRunner task_graph_runner_;
   viz::FrameSinkIdAllocator frame_sink_id_allocator_;
+  viz::SubtreeCaptureIdAllocator subtree_capture_id_allocator_;
   bool use_test_surface_;
   bool disable_vsync_ = false;
   double refresh_rate_ = 60.0;