SET: Capture opacity and filters on the shared elements.

This patch adds an extra effect node on shared elements, which on its
own does nothing but induce a render surface. This means that we can
capture that render surface and ensure that we're capturing the
output of any effects that the element's "real" effect node would
have had, such as opacity or filter.

Note that the sizing of this capture is still not correct in all cases,
but that's being tracked in follow-up bugs.

R=pdr@chromium.org, chrishtr@chromium.org

Change-Id: I361eb64ebd11ebafdf95464a483aa48e09120f43
Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/3447442
Reviewed-by: Philip Rogers <pdr@chromium.org>
Commit-Queue: Vladimir Levin <vmpstr@chromium.org>
Cr-Commit-Position: refs/heads/main@{#971209}
diff --git a/third_party/blink/renderer/core/document_transition/document_transition.cc b/third_party/blink/renderer/core/document_transition/document_transition.cc
index bab7a5d..d2616c1a 100644
--- a/third_party/blink/renderer/core/document_transition/document_transition.cc
+++ b/third_party/blink/renderer/core/document_transition/document_transition.cc
@@ -26,6 +26,7 @@
 #include "third_party/blink/renderer/core/page/chrome_client.h"
 #include "third_party/blink/renderer/core/page/page.h"
 #include "third_party/blink/renderer/core/paint/paint_layer.h"
+#include "third_party/blink/renderer/platform/graphics/compositor_element_id.h"
 #include "third_party/blink/renderer/platform/wtf/cross_thread_functional.h"
 #include "third_party/blink/renderer/platform/wtf/hash_map.h"
 #include "third_party/blink/renderer/platform/wtf/std_lib_extras.h"
@@ -99,6 +100,7 @@
   visitor->Trace(prepare_promise_resolver_);
   visitor->Trace(start_promise_resolver_);
   visitor->Trace(active_shared_elements_);
+  visitor->Trace(effect_nodes_);
   visitor->Trace(signal_);
   visitor->Trace(style_tracker_);
 
@@ -391,15 +393,22 @@
   return element && active_shared_elements_.Contains(element);
 }
 
-void DocumentTransition::PopulateSharedElementAndResourceIds(
+PaintPropertyChangeType DocumentTransition::UpdateEffect(
     const LayoutObject& object,
-    DocumentTransitionSharedElementId* shared_element_id,
-    viz::SharedElementResourceId* resource_id) const {
-  if (!IsTransitionParticipant(object))
-    return;
+    const EffectPaintPropertyNodeOrAlias& current_effect,
+    const TransformPaintPropertyNodeOrAlias* current_transform) {
+  DCHECK(IsTransitionParticipant(object));
+  DCHECK(current_transform);
 
-  *shared_element_id = DocumentTransitionSharedElementId(document_tag_);
-
+  EffectPaintPropertyNode::State state;
+  state.direct_compositing_reasons =
+      CompositingReason::kDocumentTransitionSharedElement;
+  state.local_transform_space = current_transform;
+  state.document_transition_shared_element_id =
+      DocumentTransitionSharedElementId(document_tag_);
+  state.compositor_element_id = CompositorElementIdFromUniqueObjectId(
+      object.UniqueId(),
+      CompositorElementIdNamespace::kSharedElementTransition);
   auto* element = DynamicTo<Element>(object.GetNode());
   if (!element) {
     // The only non-element participant is the layout view.
@@ -407,27 +416,69 @@
     DCHECK(!RuntimeEnabledFeatures::DocumentTransitionVizEnabled());
     // This matches one past the size of the shared element configs generated in
     // ::prepare().
-    shared_element_id->AddIndex(active_shared_elements_.size());
-    *resource_id = style_tracker_->GetLiveRootSnapshotId();
-    DCHECK(shared_element_id->valid());
-    return;
+    state.document_transition_shared_element_id.AddIndex(
+        active_shared_elements_.size());
+    state.shared_element_resource_id = style_tracker_->GetLiveRootSnapshotId();
+    DCHECK(state.document_transition_shared_element_id.valid());
+    return style_tracker_->UpdateRootEffect(std::move(state), current_effect);
   }
 
   for (wtf_size_t i = 0; i < active_shared_elements_.size(); ++i) {
     if (active_shared_elements_[i] != element)
       continue;
-    shared_element_id->AddIndex(i);
+    state.document_transition_shared_element_id.AddIndex(i);
 
     // This tags the shared element's content with the resource id used by the
     // first pseudo element. This is okay since in the eventual API we should
     // have a 1:1 mapping between shared elements and pseudo elements.
     if (!RuntimeEnabledFeatures::DocumentTransitionVizEnabled()) {
-      if (!resource_id->IsValid()) {
-        *resource_id = style_tracker_->GetLiveSnapshotId(element);
+      if (!state.shared_element_resource_id.IsValid()) {
+        state.shared_element_resource_id =
+            style_tracker_->GetLiveSnapshotId(element);
       }
     }
   }
-  DCHECK(shared_element_id->valid());
+
+  if (!RuntimeEnabledFeatures::DocumentTransitionVizEnabled()) {
+    return style_tracker_->UpdateEffect(element, std::move(state),
+                                        current_effect);
+  }
+  return UpdateEffectWithoutStyleTracker(element, std::move(state),
+                                         current_effect);
+}
+
+EffectPaintPropertyNode* DocumentTransition::GetEffect(
+    const LayoutObject& object) const {
+  DCHECK(IsTransitionParticipant(object));
+
+  auto* element = DynamicTo<Element>(object.GetNode());
+  if (!element)
+    return style_tracker_->GetRootEffect();
+
+  if (!RuntimeEnabledFeatures::DocumentTransitionVizEnabled())
+    return style_tracker_->GetEffect(element);
+
+  auto it = effect_nodes_.find(element);
+  DCHECK(it != effect_nodes_.end());
+  return it->value.get();
+}
+
+PaintPropertyChangeType DocumentTransition::UpdateEffectWithoutStyleTracker(
+    Element* element,
+    EffectPaintPropertyNode::State state,
+    const EffectPaintPropertyNodeOrAlias& current_effect) {
+  DCHECK(RuntimeEnabledFeatures::DocumentTransitionVizEnabled());
+  auto it = effect_nodes_.find(element);
+  if (it == effect_nodes_.end()) {
+    auto effect =
+        EffectPaintPropertyNode::Create(current_effect, std::move(state));
+#if DCHECK_IS_ON()
+    effect->SetDebugName("SharedElementTransition");
+#endif
+    effect_nodes_.insert(element, std::move(effect));
+    return PaintPropertyChangeType::kNodeAddedOrRemoved;
+  }
+  return it->value->Update(current_effect, std::move(state), {});
 }
 
 void DocumentTransition::VerifySharedElements() {
diff --git a/third_party/blink/renderer/core/document_transition/document_transition.h b/third_party/blink/renderer/core/document_transition/document_transition.h
index 400452c..3642f65 100644
--- a/third_party/blink/renderer/core/document_transition/document_transition.h
+++ b/third_party/blink/renderer/core/document_transition/document_transition.h
@@ -17,12 +17,9 @@
 #include "third_party/blink/renderer/core/frame/local_frame_view.h"
 #include "third_party/blink/renderer/platform/bindings/script_wrappable.h"
 #include "third_party/blink/renderer/platform/graphics/document_transition_shared_element_id.h"
+#include "third_party/blink/renderer/platform/graphics/paint/effect_paint_property_node.h"
 #include "third_party/blink/renderer/platform/wtf/wtf_size_t.h"
 
-namespace viz {
-class SharedElementResourceId;
-}
-
 namespace blink {
 
 class AbortSignal;
@@ -71,13 +68,16 @@
   // is one).
   bool IsTransitionParticipant(const LayoutObject& object) const;
 
-  // Populates |shared_element_id| and |resource_id| with identifiers for the
-  // shared element. Note that the function only modifies the ids if the object
-  // passed is a participant in the current document transition.
-  void PopulateSharedElementAndResourceIds(
-      const LayoutObject&,
-      DocumentTransitionSharedElementId* shared_element_id,
-      viz::SharedElementResourceId* resource_id) const;
+  // Updates an effect node. This effect populates the shared element id and the
+  // shared element resource id. The return value is a result of updating the
+  // effect node.
+  PaintPropertyChangeType UpdateEffect(
+      const LayoutObject& object,
+      const EffectPaintPropertyNodeOrAlias& current_effect,
+      const TransformPaintPropertyNodeOrAlias* current_transform);
+
+  // Returns the effect. One needs to first call UpdateEffect().
+  EffectPaintPropertyNode* GetEffect(const LayoutObject& object) const;
 
   // We require shared elements to be contained. This check verifies that and
   // removes it from the shared list if it isn't. See
@@ -135,6 +135,11 @@
   // finished situations.
   void ResetState(bool abort_style_tracker = true);
 
+  PaintPropertyChangeType UpdateEffectWithoutStyleTracker(
+      Element* element,
+      EffectPaintPropertyNode::State state,
+      const EffectPaintPropertyNodeOrAlias& current_effect);
+
   Member<Document> document_;
 
   State state_ = State::kIdle;
@@ -154,6 +159,9 @@
   HeapVector<Member<Element>> active_shared_elements_;
   wtf_size_t prepare_shared_element_count_ = 0u;
 
+  HeapHashMap<Member<Element>, scoped_refptr<EffectPaintPropertyNode>>
+      effect_nodes_;
+
   // Created conditionally if renderer based SharedElementTransitions is
   // enabled.
   Member<DocumentTransitionStyleTracker> style_tracker_;
diff --git a/third_party/blink/renderer/core/document_transition/document_transition_style_tracker.cc b/third_party/blink/renderer/core/document_transition/document_transition_style_tracker.cc
index 9a6c62d7..4c2ca151 100644
--- a/third_party/blink/renderer/core/document_transition/document_transition_style_tracker.cc
+++ b/third_party/blink/renderer/core/document_transition/document_transition_style_tracker.cc
@@ -118,7 +118,6 @@
   // list.
   pseudo_document_transition_tags_[0] = RootTag();
   old_root_snapshot_id_ = viz::SharedElementResourceId::Generate();
-
   element_data_map_.ReserveCapacityForSize(old_elements.size());
   for (wtf_size_t i = 0; i < old_elements.size(); ++i) {
     auto document_transition_tag = IdFromIndex(i);
@@ -167,7 +166,9 @@
     element_data->target_element = new_elements[i];
     if (new_elements[i])
       element_data->new_snapshot_id = viz::SharedElementResourceId::Generate();
+    element_data->effect_node = nullptr;
   }
+  root_effect_node_ = nullptr;
 
   // We need a style invalidation to generate new content pseudo elements for
   // new elements in the DOM.
@@ -368,6 +369,62 @@
   return has_animations;
 }
 
+PaintPropertyChangeType DocumentTransitionStyleTracker::UpdateEffect(
+    Element* element,
+    EffectPaintPropertyNode::State state,
+    const EffectPaintPropertyNodeOrAlias& current_effect) {
+  for (auto& entry : element_data_map_) {
+    auto& element_data = entry.value;
+    if (element_data->target_element != element)
+      continue;
+
+    if (!element_data->effect_node) {
+      element_data->effect_node =
+          EffectPaintPropertyNode::Create(current_effect, std::move(state));
+#if DCHECK_IS_ON()
+      element_data->effect_node->SetDebugName("SharedElementTransition");
+#endif
+      return PaintPropertyChangeType::kNodeAddedOrRemoved;
+    }
+    return element_data->effect_node->Update(current_effect, std::move(state),
+                                             {});
+  }
+  NOTREACHED();
+  return PaintPropertyChangeType::kUnchanged;
+}
+
+PaintPropertyChangeType DocumentTransitionStyleTracker::UpdateRootEffect(
+    EffectPaintPropertyNode::State state,
+    const EffectPaintPropertyNodeOrAlias& current_effect) {
+  if (!root_effect_node_) {
+    root_effect_node_ =
+        EffectPaintPropertyNode::Create(current_effect, std::move(state));
+#if DCHECK_IS_ON()
+    root_effect_node_->SetDebugName("SharedElementTransition");
+#endif
+    return PaintPropertyChangeType::kNodeAddedOrRemoved;
+  }
+  return root_effect_node_->Update(current_effect, std::move(state), {});
+}
+
+EffectPaintPropertyNode* DocumentTransitionStyleTracker::GetEffect(
+    Element* element) const {
+  for (auto& entry : element_data_map_) {
+    auto& element_data = entry.value;
+    if (element_data->target_element != element)
+      continue;
+    DCHECK(element_data->effect_node);
+    return element_data->effect_node.get();
+  }
+  NOTREACHED();
+  return nullptr;
+}
+
+EffectPaintPropertyNode* DocumentTransitionStyleTracker::GetRootEffect() const {
+  DCHECK(root_effect_node_);
+  return root_effect_node_.get();
+}
+
 void DocumentTransitionStyleTracker::InvalidateStyle() {
   ua_style_sheet_.reset();
   document_->GetStyleEngine().InvalidateUADocumentTransitionStyle();
@@ -391,6 +448,14 @@
     if (layout_view->HasSelfPaintingLayer())
       layout_view->Layer()->SetNeedsCompositingInputsUpdate();
   }
+  for (auto& entry : element_data_map_) {
+    if (!entry.value->target_element)
+      continue;
+    auto* object = entry.value->target_element->GetLayoutObject();
+    if (!object)
+      continue;
+    object->SetNeedsPaintPropertyUpdate();
+  }
 }
 
 const String& DocumentTransitionStyleTracker::UAStyleSheet() {
diff --git a/third_party/blink/renderer/core/document_transition/document_transition_style_tracker.h b/third_party/blink/renderer/core/document_transition/document_transition_style_tracker.h
index 18e4328..5a1fd02 100644
--- a/third_party/blink/renderer/core/document_transition/document_transition_style_tracker.h
+++ b/third_party/blink/renderer/core/document_transition/document_transition_style_tracker.h
@@ -10,6 +10,7 @@
 #include "components/viz/common/shared_element_resource_id.h"
 #include "third_party/blink/renderer/core/dom/element.h"
 #include "third_party/blink/renderer/platform/geometry/layout_size.h"
+#include "third_party/blink/renderer/platform/graphics/paint/effect_paint_property_node.h"
 #include "third_party/blink/renderer/platform/transforms/transformation_matrix.h"
 
 namespace blink {
@@ -85,6 +86,19 @@
   // an animation.
   bool HasActiveAnimations() const;
 
+  // Updates an effect node with the given state. The return value is a result
+  // of updating the effect node.
+  PaintPropertyChangeType UpdateEffect(
+      Element* element,
+      EffectPaintPropertyNode::State state,
+      const EffectPaintPropertyNodeOrAlias& current_effect);
+  PaintPropertyChangeType UpdateRootEffect(
+      EffectPaintPropertyNode::State state,
+      const EffectPaintPropertyNodeOrAlias& current_effect);
+
+  EffectPaintPropertyNode* GetEffect(Element* element) const;
+  EffectPaintPropertyNode* GetRootEffect() const;
+
  private:
   class ContainerPseudoElement;
 
@@ -113,6 +127,10 @@
 
     // Valid if there is an element in the new DOM generating a snapshot.
     viz::SharedElementResourceId new_snapshot_id;
+
+    // An effect used to represent the `target_element`'s contents, including
+    // any of element's own effects, in a pseudo element layer.
+    scoped_refptr<EffectPaintPropertyNode> effect_node;
   };
 
   void InvalidateStyle();
@@ -125,6 +143,7 @@
   HeapHashMap<AtomicString, Member<ElementData>> element_data_map_;
   viz::SharedElementResourceId old_root_snapshot_id_;
   viz::SharedElementResourceId new_root_snapshot_id_;
+  scoped_refptr<EffectPaintPropertyNode> root_effect_node_;
   absl::optional<String> ua_style_sheet_;
 };
 
diff --git a/third_party/blink/renderer/core/paint/paint_property_tree_builder.cc b/third_party/blink/renderer/core/paint/paint_property_tree_builder.cc
index 5ba4df04..025d721 100644
--- a/third_party/blink/renderer/core/paint/paint_property_tree_builder.cc
+++ b/third_party/blink/renderer/core/paint/paint_property_tree_builder.cc
@@ -77,17 +77,6 @@
              SubtreePaintPropertyUpdateReason::kContainerChainMayChange));
 }
 
-void PopulateSharedElementAndResourceIds(
-    const LayoutObject& object,
-    DocumentTransitionSharedElementId* shared_element_id,
-    viz::SharedElementResourceId* resource_id) {
-  if (auto* supplement =
-          DocumentTransitionSupplement::FromIfExists(object.GetDocument())) {
-    supplement->GetTransition()->PopulateSharedElementAndResourceIds(
-        object, shared_element_id, resource_id);
-  }
-}
-
 }  // namespace
 
 PaintPropertyTreeBuilderFragmentContext::
@@ -251,6 +240,7 @@
   ALWAYS_INLINE void UpdateTransform();
   ALWAYS_INLINE void UpdateTransformForSVGChild(CompositingReasons);
   ALWAYS_INLINE bool EffectCanUseCurrentClipAsOutputClip() const;
+  ALWAYS_INLINE void UpdateSharedElementTransitionEffect();
   ALWAYS_INLINE void UpdateEffect();
   ALWAYS_INLINE void UpdateFilter();
   ALWAYS_INLINE void UpdateFragmentClip();
@@ -1295,10 +1285,6 @@
       state.has_active_backdrop_filter_animation =
           style.HasCurrentBackdropFilterAnimation();
 
-      PopulateSharedElementAndResourceIds(
-          object_, &state.document_transition_shared_element_id,
-          &state.shared_element_resource_id);
-
       EffectPaintPropertyNode::AnimationState animation_state;
       animation_state.is_running_opacity_animation_on_compositor =
           style.IsRunningOpacityAnimationOnCompositor();
@@ -1400,6 +1386,21 @@
   }
 }
 
+void FragmentPaintPropertyTreeBuilder::UpdateSharedElementTransitionEffect() {
+  if (NeedsPaintPropertyUpdate()) {
+    if (full_context_.direct_compositing_reasons &
+        CompositingReason::kDocumentTransitionSharedElement) {
+      auto* supplement =
+          DocumentTransitionSupplement::FromIfExists(object_.GetDocument());
+      DCHECK(supplement);
+
+      OnUpdate(supplement->GetTransition()->UpdateEffect(
+          object_, *context_.current_effect, context_.current.transform));
+      context_.current_effect = supplement->GetTransition()->GetEffect(object_);
+    }
+  }
+}
+
 static bool IsLinkHighlighted(const LayoutObject& object) {
   return object.GetFrame()->GetPage()->GetLinkHighlight().IsHighlighting(
       object);
@@ -2810,6 +2811,7 @@
     UpdateStickyTranslation();
     UpdateTransform();
     UpdateClipPathClip();
+    UpdateSharedElementTransitionEffect();
     UpdateEffect();
     UpdateCssClip();
     UpdateFilter();
diff --git a/third_party/blink/renderer/platform/graphics/compositing_reasons.h b/third_party/blink/renderer/platform/graphics/compositing_reasons.h
index a4d807d..953d9dd3 100644
--- a/third_party/blink/renderer/platform/graphics/compositing_reasons.h
+++ b/third_party/blink/renderer/platform/graphics/compositing_reasons.h
@@ -125,8 +125,7 @@
     kDirectReasonsForEffectProperty =
         kActiveOpacityAnimation | kWillChangeOpacity | kBackdropFilter |
         kWillChangeBackdropFilter | kActiveBackdropFilterAnimation |
-        kDocumentTransitionPseudoElement | kDocumentTransitionSharedElement |
-        kTransform3DSceneLeaf,
+        kDocumentTransitionPseudoElement | kTransform3DSceneLeaf,
     kDirectReasonsForFilterProperty =
         kActiveFilterAnimation | kWillChangeFilter,
     kDirectReasonsForBackdropFilter = kBackdropFilter |
diff --git a/third_party/blink/renderer/platform/graphics/compositor_element_id.h b/third_party/blink/renderer/platform/graphics/compositor_element_id.h
index 5720765..534f912 100644
--- a/third_party/blink/renderer/platform/graphics/compositor_element_id.h
+++ b/third_party/blink/renderer/platform/graphics/compositor_element_id.h
@@ -25,6 +25,7 @@
   kEffectClipPath,
   kVerticalScrollbar,
   kHorizontalScrollbar,
+  kSharedElementTransition,
   kDOMNodeId,
   // The following values are for internal usage only.
   kMax = kDOMNodeId,
diff --git a/third_party/blink/web_tests/TestExpectations b/third_party/blink/web_tests/TestExpectations
index 9a55f12..ded9254 100644
--- a/third_party/blink/web_tests/TestExpectations
+++ b/third_party/blink/web_tests/TestExpectations
@@ -271,6 +271,10 @@
 crbug.com/1276999 wpt_internal/document-transition/* [ Skip ]
 crbug.com/1276999 virtual/document-transition/* [ Pass ]
 
+crbug.com/1295280 virtual/document-transition/wpt_internal/document-transition/old-content-captures-opacity.html [ Failure ]
+crbug.com/1295281 virtual/document-transition/wpt_internal/document-transition/new-content-captures-different-size.html [ Failure ]
+crbug.com/1295281 virtual/document-transition/wpt_internal/document-transition/old-content-captures-different-size.html [ Failure ]
+
 ########## Ref tests can't be rebaselined ##########
 crbug.com/504613 crbug.com/524248 [ Mac ] paint/images/image-backgrounds-not-antialiased.html [ Failure ]
 
diff --git a/third_party/blink/web_tests/wpt_internal/document-transition/new-content-captures-clip-path-ref.html b/third_party/blink/web_tests/wpt_internal/document-transition/new-content-captures-clip-path-ref.html
new file mode 100644
index 0000000..79da092
--- /dev/null
+++ b/third_party/blink/web_tests/wpt_internal/document-transition/new-content-captures-clip-path-ref.html
@@ -0,0 +1,23 @@
+<!DOCTYPE html>
+<title>Shared transitions: capture opacity elements (ref)</title>
+<link rel="help" href="https://github.com/WICG/shared-element-transitions">
+<link rel="author" href="mailto:vmpstr@chromium.org">
+<style>
+.box {
+  color: red;
+  background: lightgreen;
+  width: 100px;
+  height: 100px;
+  contain: paint;
+  position: absolute;
+  font-size: 30pt;
+}
+#e1 {
+  clip-path: polygon(50% 0, 100% 50%, 50% 100%, 0 50%);
+  top: 20px;
+  left: 20px;
+}
+body { background: lightpink; }
+</style>
+<div id=e1 class=box></div>
+
diff --git a/third_party/blink/web_tests/wpt_internal/document-transition/new-content-captures-clip-path.html b/third_party/blink/web_tests/wpt_internal/document-transition/new-content-captures-clip-path.html
new file mode 100644
index 0000000..8f6b1a6
--- /dev/null
+++ b/third_party/blink/web_tests/wpt_internal/document-transition/new-content-captures-clip-path.html
@@ -0,0 +1,48 @@
+<!DOCTYPE html>
+<html class=reftest-wait>
+<title>Shared transitions: capture clip-path elements</title>
+<link rel="help" href="https://github.com/WICG/shared-element-transitions">
+<link rel="author" href="mailto:vmpstr@chromium.org">
+<link rel="match" href="new-content-captures-clip-path-ref.html">
+<script src="/common/reftest-wait.js"></script>
+<style>
+.box {
+  color: red;
+  background: lightblue;
+  width: 100px;
+  height: 100px;
+  contain: paint;
+  position: absolute;
+  font-size: 30pt;
+}
+#e1 {
+  clip-path: polygon(50% 0, 100% 50%, 50% 100%, 0 50%);
+  top: 20px;
+  left: 20px;
+}
+
+div.dst { background: lightgreen; }
+/* We're verifying what we capture, so just display the new contents for 5 minutes.  */
+html::transition-container(*) { animation-duration: 300s; }
+html::transition-new-content(*) { animation-duration: 0s; opacity: 1; }
+html::transition-old-content(*) { animation-duration: 0s; opacity: 0; }
+/* hide the root so we show transition background to ensure we're in a transition */
+html::transition-container(root) { animation-duration: 0s; opacity: 0; }
+html::transition { background: lightpink; }
+</style>
+<div id=e1 class=box></div>
+<script>
+async function runTest() {
+  await document.documentTransition.prepare({
+    rootTransition: "none",
+    sharedElements: [e1]
+  });
+  e1.classList.add("dst");
+  document.documentTransition.start({
+    sharedElements: [e1]
+  });
+  requestAnimationFrame(() => requestAnimationFrame(takeScreenshot));
+}
+onload = () => requestAnimationFrame(() => requestAnimationFrame(runTest));
+</script>
+
diff --git a/third_party/blink/web_tests/wpt_internal/document-transition/new-content-captures-different-size-ref.html b/third_party/blink/web_tests/wpt_internal/document-transition/new-content-captures-different-size-ref.html
new file mode 100644
index 0000000..21260fc
--- /dev/null
+++ b/third_party/blink/web_tests/wpt_internal/document-transition/new-content-captures-different-size-ref.html
@@ -0,0 +1,36 @@
+<!DOCTYPE html>
+<title>Shared transitions: capture elements with different size capture (ref)</title>
+<link rel="help" href="https://github.com/WICG/shared-element-transitions">
+<link rel="author" href="mailto:vmpstr@chromium.org">
+<style>
+.box {
+  color: red;
+  background: lightgreen;
+  width: 100px;
+  height: 100px;
+  contain: paint;
+  position: absolute;
+  font-size: 30pt;
+}
+#e1 {
+  clip-path: circle(30%);
+  top: 20px;
+  left: 20px;
+}
+#e2 {
+  clip-path: ellipse(70% 30%);
+  top: 160px;
+  left: 20px;
+}
+#e3 {
+  filter: blur(5px);
+  top: 300px;
+  left: 20px;
+}
+
+body { background: lightpink; }
+</style>
+<div id=e1 class=box>one</div>
+<div id=e2 class=box>two</div>
+<div id=e3 class=box>three</div>
+
diff --git a/third_party/blink/web_tests/wpt_internal/document-transition/new-content-captures-different-size.html b/third_party/blink/web_tests/wpt_internal/document-transition/new-content-captures-different-size.html
new file mode 100644
index 0000000..0f67915
--- /dev/null
+++ b/third_party/blink/web_tests/wpt_internal/document-transition/new-content-captures-different-size.html
@@ -0,0 +1,62 @@
+<!DOCTYPE html>
+<html class=reftest-wait>
+<title>Shared transitions: capture elements with different size capture</title>
+<link rel="help" href="https://github.com/WICG/shared-element-transitions">
+<link rel="author" href="mailto:vmpstr@chromium.org">
+<link rel="match" href="new-content-captures-different-size-ref.html">
+<script src="/common/reftest-wait.js"></script>
+<style>
+.box {
+  color: red;
+  background: lightblue;
+  width: 100px;
+  height: 100px;
+  contain: paint;
+  position: absolute;
+  font-size: 30pt;
+}
+#e1 {
+  clip-path: circle(30%);
+  top: 20px;
+  left: 20px;
+}
+#e2 {
+  clip-path: ellipse(70% 30%);
+  top: 160px;
+  left: 20px;
+}
+#e3 {
+  filter: blur(5px);
+  top: 300px;
+  left: 20px;
+}
+
+div.dst { background: lightgreen; }
+/* We're verifying what we capture, so just display the new contents for 5 minutes.  */
+html::transition-container(*) { animation-duration: 300s; }
+html::transition-new-content(*) { animation-duration: 0s; opacity: 1; }
+html::transition-old-content(*) { animation-duration: 0s; opacity: 0; }
+/* hide the root so we show transition background to ensure we're in a transition */
+html::transition-container(root) { animation-duration: 0s; opacity: 0; }
+html::transition { background: lightpink; }
+</style>
+<div id=e1 class=box>one</div>
+<div id=e2 class=box>two</div>
+<div id=e3 class=box>three</div>
+<script>
+async function runTest() {
+  await document.documentTransition.prepare({
+    rootTransition: "none",
+    sharedElements: [e1, e2, e3]
+  });
+  e1.classList.add("dst");
+  e2.classList.add("dst");
+  e3.classList.add("dst");
+  document.documentTransition.start({
+    sharedElements: [e1, e2, e3]
+  });
+  requestAnimationFrame(() => requestAnimationFrame(takeScreenshot));
+}
+onload = () => requestAnimationFrame(() => requestAnimationFrame(runTest));
+</script>
+
diff --git a/third_party/blink/web_tests/wpt_internal/document-transition/new-content-captures-opacity-ref.html b/third_party/blink/web_tests/wpt_internal/document-transition/new-content-captures-opacity-ref.html
new file mode 100644
index 0000000..8a3100c
--- /dev/null
+++ b/third_party/blink/web_tests/wpt_internal/document-transition/new-content-captures-opacity-ref.html
@@ -0,0 +1,24 @@
+<!DOCTYPE html>
+<title>Shared transitions: capture opacity elements (ref)</title>
+<link rel="help" href="https://github.com/WICG/shared-element-transitions">
+<link rel="author" href="mailto:vmpstr@chromium.org">
+<style>
+.box {
+  color: red;
+  background: lightgreen;
+  width: 100px;
+  height: 100px;
+  contain: paint;
+  position: absolute;
+  font-size: 30pt;
+  will-change: opacity;
+}
+#e1 { opacity: 0.75; top: 20px; left: 20px; }
+#e2 { opacity: 0.5; top: 160px; left: 20px; }
+#e3 { opacity: 0.25; top: 300px; left: 20px; }
+body { background: lightpink; }
+</style>
+<div id=e1 class=box>one</div>
+<div id=e2 class=box>two</div>
+<div id=e3 class=box>three</div>
+
diff --git a/third_party/blink/web_tests/wpt_internal/document-transition/new-content-captures-opacity.html b/third_party/blink/web_tests/wpt_internal/document-transition/new-content-captures-opacity.html
new file mode 100644
index 0000000..f77a2c4
--- /dev/null
+++ b/third_party/blink/web_tests/wpt_internal/document-transition/new-content-captures-opacity.html
@@ -0,0 +1,50 @@
+<!DOCTYPE html>
+<html class=reftest-wait>
+<title>Shared transitions: capture opacity elements</title>
+<link rel="help" href="https://github.com/WICG/shared-element-transitions">
+<link rel="author" href="mailto:vmpstr@chromium.org">
+<link rel="match" href="new-content-captures-opacity-ref.html">
+<script src="/common/reftest-wait.js"></script>
+<style>
+.box {
+  color: red;
+  background: lightblue;
+  width: 100px;
+  height: 100px;
+  contain: paint;
+  position: absolute;
+  font-size: 30pt;
+  will-change: opacity;
+}
+#e1 { opacity: 0.75; top: 20px; left: 20px; }
+#e2 { opacity: 0.5; top: 160px; left: 20px; }
+#e3 { opacity: 0.25; top: 300px; left: 20px; }
+div.dst { background: lightgreen; }
+/* We're verifying what we capture, so just display the new contents for 5 minutes.  */
+html::transition-container(*) { animation-duration: 300s; }
+html::transition-new-content(*) { animation-duration: 0s; opacity: 1; }
+html::transition-old-content(*) { animation-duration: 0s; opacity: 0; }
+/* hide the root so we show transition background to ensure we're in a transition */
+html::transition-container(root) { animation-duration: 0s; opacity: 0; }
+html::transition { background: lightpink; }
+</style>
+<div id=e1 class=box>one</div>
+<div id=e2 class=box>two</div>
+<div id=e3 class=box>three</div>
+<script>
+async function runTest() {
+  await document.documentTransition.prepare({
+    rootTransition: "none",
+    sharedElements: [e1, e2, e3]
+  });
+  e1.classList.add("dst");
+  e2.classList.add("dst");
+  e3.classList.add("dst");
+  document.documentTransition.start({
+    sharedElements: [e1, e2, e3]
+  });
+  requestAnimationFrame(() => requestAnimationFrame(takeScreenshot));
+}
+onload = () => requestAnimationFrame(() => requestAnimationFrame(runTest));
+</script>
+
diff --git a/third_party/blink/web_tests/wpt_internal/document-transition/old-content-captures-clip-path-ref.html b/third_party/blink/web_tests/wpt_internal/document-transition/old-content-captures-clip-path-ref.html
new file mode 100644
index 0000000..936eb22
--- /dev/null
+++ b/third_party/blink/web_tests/wpt_internal/document-transition/old-content-captures-clip-path-ref.html
@@ -0,0 +1,22 @@
+<!DOCTYPE html>
+<title>Shared transitions: capture clip-path elements (ref)</title>
+<link rel="help" href="https://github.com/WICG/shared-element-transitions">
+<link rel="author" href="mailto:vmpstr@chromium.org">
+<style>
+.box {
+  color: red;
+  background: lightblue;
+  width: 100px;
+  height: 100px;
+  contain: paint;
+  position: absolute;
+  font-size: 30pt;
+}
+#e1 {
+  clip-path: polygon(50% 0, 100% 50%, 50% 100%, 0 50%);
+  top: 20px;
+  left: 20px;
+}
+body { background: lightpink; }
+</style>
+<div id=e1 class=box></div>
diff --git a/third_party/blink/web_tests/wpt_internal/document-transition/old-content-captures-clip-path.html b/third_party/blink/web_tests/wpt_internal/document-transition/old-content-captures-clip-path.html
new file mode 100644
index 0000000..b909013
--- /dev/null
+++ b/third_party/blink/web_tests/wpt_internal/document-transition/old-content-captures-clip-path.html
@@ -0,0 +1,48 @@
+<!DOCTYPE html>
+<html class=reftest-wait>
+<title>Shared transitions: capture clip-path elements</title>
+<link rel="help" href="https://github.com/WICG/shared-element-transitions">
+<link rel="author" href="mailto:vmpstr@chromium.org">
+<link rel="match" href="old-content-captures-clip-path-ref.html">
+<script src="/common/reftest-wait.js"></script>
+<style>
+.box {
+  color: red;
+  background: lightblue;
+  width: 100px;
+  height: 100px;
+  contain: paint;
+  position: absolute;
+  font-size: 30pt;
+}
+#e1 {
+  clip-path: polygon(50% 0, 100% 50%, 50% 100%, 0 50%);
+  top: 20px;
+  left: 20px;
+}
+
+div.dst { background: lightgreen; }
+/* We're verifying what we capture, so just display the old contents for 5 minutes.  */
+html::transition-container(*) { animation-duration: 300s; }
+html::transition-new-content(*) { animation-duration: 0s; opacity: 0; }
+html::transition-old-content(*) { animation-duration: 0s; opacity: 1; }
+/* hide the root so we show transition background to ensure we're in a transition */
+html::transition-container(root) { animation-duration: 0s; opacity: 0; }
+html::transition { background: lightpink; }
+</style>
+<div id=e1 class=box></div>
+<script>
+async function runTest() {
+  await document.documentTransition.prepare({
+    rootTransition: "none",
+    sharedElements: [e1]
+  });
+  e1.classList.add("dst");
+  document.documentTransition.start({
+    sharedElements: [e1]
+  });
+  requestAnimationFrame(() => requestAnimationFrame(takeScreenshot));
+}
+onload = () => requestAnimationFrame(() => requestAnimationFrame(runTest));
+</script>
+
diff --git a/third_party/blink/web_tests/wpt_internal/document-transition/old-content-captures-different-size-ref.html b/third_party/blink/web_tests/wpt_internal/document-transition/old-content-captures-different-size-ref.html
new file mode 100644
index 0000000..b42952d1
--- /dev/null
+++ b/third_party/blink/web_tests/wpt_internal/document-transition/old-content-captures-different-size-ref.html
@@ -0,0 +1,35 @@
+<!DOCTYPE html>
+<title>Shared transitions: capture elements with different size capture (ref)</title>
+<link rel="help" href="https://github.com/WICG/shared-element-transitions">
+<link rel="author" href="mailto:vmpstr@chromium.org">
+<style>
+.box {
+  color: red;
+  background: lightblue;
+  width: 100px;
+  height: 100px;
+  contain: paint;
+  position: absolute;
+  font-size: 30pt;
+}
+#e1 {
+  clip-path: circle(30%);
+  top: 20px;
+  left: 20px;
+}
+#e2 {
+  clip-path: ellipse(70% 30%);
+  top: 160px;
+  left: 20px;
+}
+#e3 {
+  clip-path: blur(5px);
+  top: 300px;
+  left: 20px;
+}
+body { background: lightpink; }
+</style>
+<div id=e1 class=box>one</div>
+<div id=e2 class=box>two</div>
+<div id=e3 class=box>three</div>
+
diff --git a/third_party/blink/web_tests/wpt_internal/document-transition/old-content-captures-different-size.html b/third_party/blink/web_tests/wpt_internal/document-transition/old-content-captures-different-size.html
new file mode 100644
index 0000000..383e953
--- /dev/null
+++ b/third_party/blink/web_tests/wpt_internal/document-transition/old-content-captures-different-size.html
@@ -0,0 +1,62 @@
+<!DOCTYPE html>
+<html class=reftest-wait>
+<title>Shared transitions: capture elements with different size capture</title>
+<link rel="help" href="https://github.com/WICG/shared-element-transitions">
+<link rel="author" href="mailto:vmpstr@chromium.org">
+<link rel="match" href="old-content-captures-different-size-ref.html">
+<script src="/common/reftest-wait.js"></script>
+<style>
+.box {
+  color: red;
+  background: lightblue;
+  width: 100px;
+  height: 100px;
+  contain: paint;
+  position: absolute;
+  font-size: 30pt;
+}
+#e1 {
+  clip-path: circle(30%);
+  top: 20px;
+  left: 20px;
+}
+#e2 {
+  clip-path: ellipse(70% 30%);
+  top: 160px;
+  left: 20px;
+}
+#e3 {
+  filter: blur(5px);
+  top: 300px;
+  left: 20px;
+}
+
+div.dst { background: lightgreen; }
+/* We're verifying what we capture, so just display the old contents for 5 minutes.  */
+html::transition-container(*) { animation-duration: 300s; }
+html::transition-new-content(*) { animation-duration: 0s; opacity: 0; }
+html::transition-old-content(*) { animation-duration: 0s; opacity: 1; }
+/* hide the root so we show transition background to ensure we're in a transition */
+html::transition-container(root) { animation-duration: 0s; opacity: 0; }
+html::transition { background: lightpink; }
+</style>
+<div id=e1 class=box>one</div>
+<div id=e2 class=box>two</div>
+<div id=e3 class=box>three</div>
+<script>
+async function runTest() {
+  await document.documentTransition.prepare({
+    rootTransition: "none",
+    sharedElements: [e1, e2, e3]
+  });
+  e1.classList.add("dst");
+  e2.classList.add("dst");
+  e3.classList.add("dst");
+  document.documentTransition.start({
+    sharedElements: [e1, e2, e3]
+  });
+  requestAnimationFrame(() => requestAnimationFrame(takeScreenshot));
+}
+onload = () => requestAnimationFrame(() => requestAnimationFrame(runTest));
+</script>
+
diff --git a/third_party/blink/web_tests/wpt_internal/document-transition/old-content-captures-opacity-ref.html b/third_party/blink/web_tests/wpt_internal/document-transition/old-content-captures-opacity-ref.html
new file mode 100644
index 0000000..44fa1903
--- /dev/null
+++ b/third_party/blink/web_tests/wpt_internal/document-transition/old-content-captures-opacity-ref.html
@@ -0,0 +1,24 @@
+<!DOCTYPE html>
+<title>Shared transitions: capture opacity elements (ref)</title>
+<link rel="help" href="https://github.com/WICG/shared-element-transitions">
+<link rel="author" href="mailto:vmpstr@chromium.org">
+<style>
+.box {
+  color: red;
+  background: lightblue;
+  width: 100px;
+  height: 100px;
+  contain: paint;
+  position: absolute;
+  font-size: 30pt;
+  will-change: opacity;
+}
+#e1 { opacity: 0.75; top: 20px; left: 20px; }
+#e2 { opacity: 0.5; top: 160px; left: 20px; }
+#e3 { opacity: 0.25; top: 300px; left: 20px; }
+body { background: lightpink; }
+</style>
+<div id=e1 class=box>one</div>
+<div id=e2 class=box>two</div>
+<div id=e3 class=box>three</div>
+
diff --git a/third_party/blink/web_tests/wpt_internal/document-transition/old-content-captures-opacity.html b/third_party/blink/web_tests/wpt_internal/document-transition/old-content-captures-opacity.html
new file mode 100644
index 0000000..4c209000
--- /dev/null
+++ b/third_party/blink/web_tests/wpt_internal/document-transition/old-content-captures-opacity.html
@@ -0,0 +1,51 @@
+<!DOCTYPE html>
+<html class=reftest-wait>
+<title>Shared transitions: capture opacity elements</title>
+<link rel="help" href="https://github.com/WICG/shared-element-transitions">
+<link rel="author" href="mailto:vmpstr@chromium.org">
+<link rel="match" href="old-content-captures-opacity-ref.html">
+<meta name=fuzzy content="maxDifference=10;totalPixels=30000">
+
+<script src="/common/reftest-wait.js"></script>
+<style>
+.box {
+  color: red;
+  background: lightblue;
+  width: 100px;
+  height: 100px;
+  contain: paint;
+  position: absolute;
+  font-size: 30pt;
+}
+#e1 { opacity: 0.75; top: 20px; left: 20px; }
+#e2 { opacity: 0.5; top: 160px; left: 20px; }
+#e3 { opacity: 0.25; top: 300px; left: 20px; }
+div.dst { background: lightgreen; }
+/* We're verifying what we capture, so just display the old contents for 5 minutes.  */
+html::transition-container(*) { animation-duration: 300s; }
+html::transition-new-content(*) { animation-duration: 0s; opacity: 0; }
+html::transition-old-content(*) { animation-duration: 0s; opacity: 1; }
+/* hide the root so we show transition background to ensure we're in a transition */
+html::transition-container(root) { animation-duration: 0s; opacity: 0; }
+html::transition { background: lightpink; }
+</style>
+<div id=e1 class=box>one</div>
+<div id=e2 class=box>two</div>
+<div id=e3 class=box>three</div>
+<script>
+async function runTest() {
+  await document.documentTransition.prepare({
+    rootTransition: "none",
+    sharedElements: [e1, e2, e3]
+  });
+  e1.classList.add("dst");
+  e2.classList.add("dst");
+  e3.classList.add("dst");
+  document.documentTransition.start({
+    sharedElements: [e1, e2, e3]
+  });
+  requestAnimationFrame(() => requestAnimationFrame(takeScreenshot));
+}
+onload = () => requestAnimationFrame(() => requestAnimationFrame(runTest));
+</script>
+