0

SET: Respect source element opacity in pseudo element display.

This patch propagates the opacity from the source shared element to
the pseudo that is used to represent it. For old content (mode 1) we
only update it when we're preparing to "bake" opacity in. For new
content we always update it to reflect the possibility of changing
opacity.

R=chrishtr@chromium.org

Change-Id: I734315fe4fd780f8ee19cb95073a75d757c2bb82
Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/3437145
Reviewed-by: Chris Harrelson <chrishtr@chromium.org>
Commit-Queue: Vladimir Levin <vmpstr@chromium.org>
Cr-Commit-Position: refs/heads/main@{#967268}
This commit is contained in:
Vladimir Levin
2022-02-04 16:02:50 +00:00
committed by Chromium LUCI CQ
parent c5c0063b8d
commit 2af7533525
15 changed files with 360 additions and 12 deletions

@ -29,10 +29,33 @@ DocumentTransitionContentLayer::DocumentTransitionResourceId() const {
return resource_id_;
}
void DocumentTransitionContentLayer::SetSourceOpacity(float opacity) {
source_opacity_ = opacity;
SetNeedsPushProperties();
}
void DocumentTransitionContentLayer::PushPropertiesTo(
LayerImpl* layer,
const CommitState& commit_state,
const ThreadUnsafeCommitState& unsafe_state) {
Layer::PushPropertiesTo(layer, commit_state, unsafe_state);
auto* content_layer_impl =
static_cast<DocumentTransitionContentLayerImpl*>(layer);
PushLocalPropertiesTo(content_layer_impl);
}
void DocumentTransitionContentLayer::PushLocalPropertiesTo(
DocumentTransitionContentLayerImpl* layer_impl) const {
layer_impl->SetSourceOpacity(source_opacity_);
}
std::unique_ptr<LayerImpl> DocumentTransitionContentLayer::CreateLayerImpl(
LayerTreeImpl* tree_impl) const {
return DocumentTransitionContentLayerImpl::Create(tree_impl, id(),
resource_id_);
auto layer =
DocumentTransitionContentLayerImpl::Create(tree_impl, id(), resource_id_);
PushLocalPropertiesTo(layer.get());
return layer;
}
} // namespace cc

@ -13,6 +13,7 @@
#include "components/viz/common/shared_element_resource_id.h"
namespace cc {
class DocumentTransitionContentLayerImpl;
// A layer that renders a texture cached in the Viz process.
class CC_EXPORT DocumentTransitionContentLayer : public Layer {
@ -27,9 +28,18 @@ class CC_EXPORT DocumentTransitionContentLayer : public Layer {
viz::SharedElementResourceId DocumentTransitionResourceId() const override;
// Set the source opacity, which is the opacity specified on the shared
// element that this layer draws. This is multiplied by any of this layer's
// own opacity to give the effect that the source shared element was captured
// with its opacity preserved.
void SetSourceOpacity(float opacity);
// Layer overrides.
std::unique_ptr<LayerImpl> CreateLayerImpl(
LayerTreeImpl* tree_impl) const override;
void PushPropertiesTo(LayerImpl* layer,
const CommitState& commit_state,
const ThreadUnsafeCommitState& unsafe_state) override;
protected:
explicit DocumentTransitionContentLayer(
@ -38,7 +48,12 @@ class CC_EXPORT DocumentTransitionContentLayer : public Layer {
private:
~DocumentTransitionContentLayer() override;
void PushLocalPropertiesTo(
DocumentTransitionContentLayerImpl* layer_impl) const;
const viz::SharedElementResourceId resource_id_;
float source_opacity_ = 1.f;
};
} // namespace cc

@ -33,8 +33,10 @@ DocumentTransitionContentLayerImpl::~DocumentTransitionContentLayerImpl() =
std::unique_ptr<LayerImpl> DocumentTransitionContentLayerImpl::CreateLayerImpl(
LayerTreeImpl* tree_impl) const {
return DocumentTransitionContentLayerImpl::Create(tree_impl, id(),
resource_id_);
auto layer =
DocumentTransitionContentLayerImpl::Create(tree_impl, id(), resource_id_);
PushLocalPropertiesTo(layer.get());
return layer;
}
void DocumentTransitionContentLayerImpl::AppendQuads(
@ -59,6 +61,7 @@ void DocumentTransitionContentLayerImpl::AppendQuads(
render_pass->CreateAndAppendSharedQuadState();
PopulateScaledSharedQuadState(shared_quad_state, device_scale_factor,
contents_opaque());
shared_quad_state->opacity *= source_opacity_;
auto* quad =
render_pass->CreateAndAppendDrawQuad<viz::SharedElementDrawQuad>();
@ -66,6 +69,24 @@ void DocumentTransitionContentLayerImpl::AppendQuads(
append_quads_data->has_shared_element_resources = true;
}
void DocumentTransitionContentLayerImpl::PushPropertiesTo(LayerImpl* layer) {
LayerImpl::PushPropertiesTo(layer);
auto* content_layer_impl =
static_cast<DocumentTransitionContentLayerImpl*>(layer);
PushLocalPropertiesTo(content_layer_impl);
}
void DocumentTransitionContentLayerImpl::PushLocalPropertiesTo(
DocumentTransitionContentLayerImpl* layer_impl) const {
layer_impl->SetSourceOpacity(source_opacity_);
}
void DocumentTransitionContentLayerImpl::SetSourceOpacity(float opacity) {
source_opacity_ = opacity;
SetNeedsPushProperties();
}
const char* DocumentTransitionContentLayerImpl::LayerTypeAsString() const {
return "cc::DocumentTransitionContentLayerImpl";
}

@ -27,11 +27,18 @@ class CC_EXPORT DocumentTransitionContentLayerImpl : public LayerImpl {
DocumentTransitionContentLayerImpl& operator=(
const DocumentTransitionContentLayerImpl&) = delete;
// Set the source opacity, which is the opacity specified on the shared
// element that this layer draws. This is multiplied by any of this layer's
// own opacity to give the effect that the source shared element was captured
// with its opacity preserved.
void SetSourceOpacity(float opacity);
// LayerImpl overrides.
std::unique_ptr<LayerImpl> CreateLayerImpl(
LayerTreeImpl* tree_impl) const override;
void AppendQuads(viz::CompositorRenderPass* render_pass,
AppendQuadsData* append_quads_data) override;
void PushPropertiesTo(LayerImpl* layer) override;
protected:
DocumentTransitionContentLayerImpl(
@ -42,7 +49,12 @@ class CC_EXPORT DocumentTransitionContentLayerImpl : public LayerImpl {
private:
const char* LayerTypeAsString() const override;
void PushLocalPropertiesTo(
DocumentTransitionContentLayerImpl* layer_impl) const;
const viz::SharedElementResourceId resource_id_;
float source_opacity_ = 1.f;
};
} // namespace cc

@ -5,6 +5,7 @@
#include "third_party/blink/renderer/core/document_transition/document_transition_content_element.h"
#include "third_party/blink/renderer/core/layout/layout_document_transition_content.h"
#include "third_party/blink/renderer/core/style/computed_style.h"
namespace blink {
@ -25,16 +26,31 @@ DocumentTransitionContentElement::~DocumentTransitionContentElement() = default;
void DocumentTransitionContentElement::SetIntrinsicSize(
const LayoutSize& intrinsic_size) {
intrinsic_size_ = intrinsic_size;
if (auto* layout_object = GetLayoutObject()) {
static_cast<LayoutDocumentTransitionContent*>(layout_object)
->OnIntrinsicSizeUpdated(intrinsic_size_);
}
UpdateLayoutObjectFromSourceStyle(GetLayoutObject());
}
void DocumentTransitionContentElement::UpdateFromSourceStyle(
const ComputedStyle* style) {
source_opacity_ = style ? style->Opacity() : 1.f;
UpdateLayoutObjectFromSourceStyle(GetLayoutObject());
}
LayoutObject*
DocumentTransitionContentElement::UpdateLayoutObjectFromSourceStyle(
LayoutObject* object) const {
if (!object)
return nullptr;
auto* content_object = static_cast<LayoutDocumentTransitionContent*>(object);
content_object->SetIntrinsicSize(intrinsic_size_);
content_object->SetSourceOpacity(source_opacity_);
return content_object;
}
LayoutObject* DocumentTransitionContentElement::CreateLayoutObject(
const ComputedStyle&,
LegacyLayout) {
return MakeGarbageCollected<LayoutDocumentTransitionContent>(this);
return UpdateLayoutObjectFromSourceStyle(
MakeGarbageCollected<LayoutDocumentTransitionContent>(this));
}
} // namespace blink

@ -14,6 +14,8 @@
namespace blink {
class ComputedStyle;
// This class implements the functionality to display a live or cached snapshot
// of an element created using content:element(id).
// The element function is described at
@ -34,15 +36,22 @@ class CORE_EXPORT DocumentTransitionContentElement
return resource_id_;
}
void UpdateFromSourceStyle(const ComputedStyle* style);
private:
LayoutObject* CreateLayoutObject(const ComputedStyle&, LegacyLayout) override;
LayoutObject* UpdateLayoutObjectFromSourceStyle(LayoutObject* object) const;
// |resource_id_| is used to generate a foreign layer to substitute this
// element with a render pass generated by the compositor.
const viz::SharedElementResourceId resource_id_;
// The size of the element's texture generated by the compositor.
LayoutSize intrinsic_size_;
// Opacity used on the source shared element.
float source_opacity_ = 1.f;
};
} // namespace blink

@ -109,6 +109,7 @@ void DocumentTransitionStyleTracker::Prepare(
DCHECK_EQ(state_, State::kIdle);
state_ = State::kPreparing;
has_had_first_post_layout_after_prepare_ = true;
// An id for each shared element + root.
pseudo_document_transition_tags_.resize(old_elements.size() + 1);
@ -311,6 +312,21 @@ void DocumentTransitionStyleTracker::RunPostLayoutSteps() {
continue;
}
if (state_ == State::kPreparing &&
has_had_first_post_layout_after_prepare_) {
auto* old_content_element =
DocumentTransitionUtils::FindOldContentElement(*document_, entry.key);
DCHECK(old_content_element);
old_content_element->UpdateFromSourceStyle(
element_data->target_element->GetComputedStyle());
}
if (auto* new_content_element =
DocumentTransitionUtils::FindNewContentElement(*document_,
entry.key)) {
new_content_element->UpdateFromSourceStyle(
element_data->target_element->GetComputedStyle());
}
const auto& viewport_matrix = layout_object->LocalToAbsoluteTransform();
// ResizeObserverEntry is created to reuse the logic for parsing object size
@ -346,6 +362,21 @@ void DocumentTransitionStyleTracker::RunPostLayoutSteps() {
if (needs_style_invalidation)
InvalidateStyle();
if (state_ == State::kPreparing && has_had_first_post_layout_after_prepare_) {
auto* old_content_element =
DocumentTransitionUtils::FindOldContentElement(*document_, RootTag());
DCHECK(old_content_element);
old_content_element->UpdateFromSourceStyle(
document_->documentElement()->GetComputedStyle());
}
if (auto* new_content_element =
DocumentTransitionUtils::FindNewContentElement(*document_,
RootTag())) {
new_content_element->UpdateFromSourceStyle(
document_->documentElement()->GetComputedStyle());
}
has_had_first_post_layout_after_prepare_ = false;
}
bool DocumentTransitionStyleTracker::HasActiveAnimations() const {

@ -126,6 +126,11 @@ class DocumentTransitionStyleTracker
viz::SharedElementResourceId old_root_snapshot_id_;
viz::SharedElementResourceId new_root_snapshot_id_;
absl::optional<String> ua_style_sheet_;
// If true, then we had a prepare call and did not yet have a
// RunPostLayoutSteps. This is tracked to determine whether we're in first
// post-layout steps after we started preparing.
bool has_had_first_post_layout_after_prepare_ = false;
};
} // namespace blink

@ -6,6 +6,7 @@
#define THIRD_PARTY_BLINK_RENDERER_CORE_DOCUMENT_TRANSITION_DOCUMENT_TRANSITION_UTILS_H_
#include "third_party/blink/renderer/core/core_export.h"
#include "third_party/blink/renderer/core/document_transition/document_transition_content_element.h"
#include "third_party/blink/renderer/core/dom/document.h"
#include "third_party/blink/renderer/core/dom/pseudo_element.h"
#include "third_party/blink/renderer/core/style/computed_style_constants.h"
@ -41,6 +42,38 @@ class CORE_EXPORT DocumentTransitionUtils {
func(content);
}
}
static DocumentTransitionContentElement* FindOldContentElement(
Document& document,
const AtomicString& tag) {
return FindContentElement(document, tag, kPseudoIdTransitionOldContent);
}
static DocumentTransitionContentElement* FindNewContentElement(
Document& document,
const AtomicString& tag) {
return FindContentElement(document, tag, kPseudoIdTransitionNewContent);
}
private:
static DocumentTransitionContentElement*
FindContentElement(Document& document, const AtomicString& tag, PseudoId id) {
DCHECK(id == kPseudoIdTransitionOldContent ||
id == kPseudoIdTransitionNewContent);
auto* transition_pseudo =
document.documentElement()->GetPseudoElement(kPseudoIdTransition);
if (!transition_pseudo)
return nullptr;
auto* container_pseudo =
transition_pseudo->GetPseudoElement(kPseudoIdTransitionContainer, tag);
if (!container_pseudo)
return nullptr;
return static_cast<DocumentTransitionContentElement*>(
container_pseudo->GetPseudoElement(id, tag));
}
};
} // namespace blink

@ -13,9 +13,7 @@ LayoutDocumentTransitionContent::LayoutDocumentTransitionContent(
DocumentTransitionContentElement* element)
: LayoutReplaced(element),
layer_(
cc::DocumentTransitionContentLayer::Create(element->resource_id())) {
SetIntrinsicSize(element->intrinsic_size());
}
cc::DocumentTransitionContentLayer::Create(element->resource_id())) {}
LayoutDocumentTransitionContent::~LayoutDocumentTransitionContent() = default;
@ -38,6 +36,10 @@ LayoutDocumentTransitionContent::AdditionalCompositingReasons() const {
return CompositingReason::kDocumentTransitionContentElement;
}
void LayoutDocumentTransitionContent::SetSourceOpacity(float opacity) {
layer_->SetSourceOpacity(opacity);
}
void LayoutDocumentTransitionContent::PaintReplaced(
const PaintInfo& paint_info,
const PhysicalOffset& paint_offset) const {

@ -23,6 +23,9 @@ class CORE_EXPORT LayoutDocumentTransitionContent : public LayoutReplaced {
return "LayoutDocumentTransitionContent";
}
void OnIntrinsicSizeUpdated(const LayoutSize& intrinsic_size);
void SetSourceOpacity(float opacity);
using LayoutReplaced::SetIntrinsicSize;
protected:
PaintLayerType LayerTypeRequired() const override;

@ -0,0 +1,27 @@
<!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>

@ -0,0 +1,62 @@
<!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>

@ -0,0 +1,27 @@
<!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>

@ -0,0 +1,62 @@
<!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">
<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 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>