Skip to content

Commit

Permalink
[view-transitions] Implement "capture the image" algorithm.
Browse files Browse the repository at this point in the history
https://bugs.webkit.org/show_bug.cgi?id=265170
<rdar://118667021>

Reviewed by Tim Nguyen and Simon Fraser.

Uses 'snapshotNode' to capture the old image during 'captureOldState', and
implements `paintReplaced` on the renderer (in the same way RenderHTMLElement
does) to draw the old catpured image.

Forces composited layers to be created for the ::view-transition-new/old pseudos,
as well as the element with the view-transtion, and reparents the GraphicsLayer
from real element into pseduo, so that the new capture is displayed using the
live content. This also effectively stops the original element from being displayed
normally, as required by the spec.

* Source/WebCore/dom/ViewTransition.cpp:
(WebCore::ViewTransition::captureOldState):
* Source/WebCore/dom/ViewTransition.h:
* Source/WebCore/rendering/RenderBoxModelObject.cpp:
(WebCore::RenderBoxModelObject::requiresLayer const):
* Source/WebCore/rendering/RenderElement.cpp:
(WebCore::RenderElement::hasViewTransition const):
(WebCore::RenderElement::isViewTransitionPseudo const):
* Source/WebCore/rendering/RenderElement.h:
* Source/WebCore/rendering/RenderLayer.cpp:
(WebCore::canCreateStackingContext):
(WebCore::RenderLayer::shouldBeCSSStackingContext const):
(WebCore::RenderLayer::computeCanBeBackdropRoot const):
* Source/WebCore/rendering/RenderLayerBacking.cpp:
(WebCore::RenderLayerBacking::updateGeometry):
* Source/WebCore/rendering/RenderLayerCompositor.cpp:
(WebCore::RenderLayerCompositor::collectViewTransitionNewContentLayers):
(WebCore::RenderLayerCompositor::updateBackingAndHierarchy):
(WebCore::RenderLayerCompositor::requiresCompositingLayer const):
(WebCore::RenderLayerCompositor::requiresOwnBackingStore const):
(WebCore::RenderLayerCompositor::requiresCompositingForViewTransition const):
* Source/WebCore/rendering/RenderLayerCompositor.h:
* Source/WebCore/rendering/RenderViewTransitionCapture.cpp:
(WebCore::RenderViewTransitionCapture::setImage):
(WebCore::RenderViewTransition::paintReplaced):
* Source/WebCore/rendering/RenderViewTransitionCapture.h:
* Source/WebCore/rendering/updating/RenderTreeUpdaterViewTransition.cpp:
(WebCore::RenderTreeUpdater::ViewTransition::updatePseudoElementTree):
(WebCore::RenderTreeUpdater::ViewTransition::buildPseudoElementGroup):
(WebCore::RenderTreeUpdater::ViewTransition::updatePseudoElementGroup):

Canonical link: https://commits.webkit.org/274957@main
  • Loading branch information
mattwoodrow authored and nt1m committed Feb 19, 2024
1 parent de9e3d6 commit 115f020
Show file tree
Hide file tree
Showing 13 changed files with 112 additions and 11 deletions.
3 changes: 3 additions & 0 deletions LayoutTests/platform/glib/TestExpectations
Original file line number Diff line number Diff line change
Expand Up @@ -3852,6 +3852,9 @@ webkit.org/b/186667 compositing/repaint/iframes/composited-iframe-with-fixed-bac
# Probably categorized under the wrong bug.
webkit.org/b/212202 compositing/fixed-with-main-thread-scrolling.html [ Timeout ]

imported/w3c/web-platform-tests/css/css-view-transitions/backdrop-filter-captured.html [ ImageOnlyFailure ]
imported/w3c/web-platform-tests/css/css-view-transitions/element-stops-grouping-after-animation.html [ ImageOnlyFailure ]

# End: Common failures between GTK and WPE.

#////////////////////////////////////////////////////////////////////////////////////////
Expand Down
5 changes: 5 additions & 0 deletions Source/WebCore/dom/ViewTransition.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,7 @@
#include "CheckVisibilityOptions.h"
#include "ComputedStyleExtractor.h"
#include "Document.h"
#include "FrameSnapshotting.h"
#include "JSDOMPromise.h"
#include "JSDOMPromiseDeferred.h"
#include "PseudoElementRequest.h"
Expand Down Expand Up @@ -282,9 +283,13 @@ ExceptionOr<void> ViewTransition::captureOldState()
if (renderBox)
capture.oldSize = renderBox->size();
capture.oldProperties = copyElementBaseProperties(element.get());
if (m_document->frame())
capture.oldImage = snapshotNode(*m_document->frame(), element.get(), { { }, PixelFormat::BGRA8, DestinationColorSpace::SRGB() });

auto transitionName = element->computedStyle()->viewTransitionName();
m_namedElements.add(transitionName->name, capture);

element->invalidateStyleAndLayerComposition();
}
return { };
}
Expand Down
3 changes: 2 additions & 1 deletion Source/WebCore/dom/ViewTransition.h
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,7 @@
#include "Document.h"
#include "Element.h"
#include "ExceptionOr.h"
#include "ImageBuffer.h"
#include "JSValueInWrappedObject.h"
#include "MutableStyleProperties.h"
#include "ViewTransitionUpdateCallback.h"
Expand All @@ -52,7 +53,7 @@ struct CapturedElement {
WTF_MAKE_FAST_ALLOCATED;
public:
// FIXME: Add the following:
// old image (2d bitmap)
RefPtr<ImageBuffer> oldImage;
LayoutSize oldSize;
RefPtr<MutableStyleProperties> oldProperties;
// old transform
Expand Down
2 changes: 1 addition & 1 deletion Source/WebCore/rendering/RenderBoxModelObject.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -970,7 +970,7 @@ void RenderBoxModelObject::applyTransform(TransformationMatrix&, const RenderSty

bool RenderBoxModelObject::requiresLayer() const
{
return isDocumentElementRenderer() || isPositioned() || createsGroup() || hasTransformRelatedProperty() || hasHiddenBackface() || hasReflection();
return isDocumentElementRenderer() || isPositioned() || createsGroup() || hasTransformRelatedProperty() || hasHiddenBackface() || hasReflection() || hasViewTransition() || isViewTransitionPseudo();
}

} // namespace WebCore
14 changes: 14 additions & 0 deletions Source/WebCore/rendering/RenderElement.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -95,6 +95,7 @@
#include "StyleResolver.h"
#include "Styleable.h"
#include "TextAutoSizing.h"
#include "ViewTransition.h"
#include <wtf/IsoMallocInlines.h>
#include <wtf/MathExtras.h>
#include <wtf/StackStats.h>
Expand Down Expand Up @@ -2063,6 +2064,19 @@ bool RenderElement::hasSelfPaintingLayer() const
return layerModelObject.hasSelfPaintingLayer();
}

bool RenderElement::hasViewTransition() const
{
if (!style().viewTransitionName())
return false;

return !!document().activeViewTransition();
}

bool RenderElement::isViewTransitionPseudo() const
{
return style().pseudoElementType() == PseudoId::ViewTransitionNew || style().pseudoElementType() == PseudoId::ViewTransitionOld;
}

bool RenderElement::checkForRepaintDuringLayout() const
{
return everHadLayout() && !hasSelfPaintingLayer() && !document().view()->layoutContext().needsFullRepaint();
Expand Down
2 changes: 2 additions & 0 deletions Source/WebCore/rendering/RenderElement.h
Original file line number Diff line number Diff line change
Expand Up @@ -191,6 +191,8 @@ class RenderElement : public RenderObject {
inline bool hasClipOrNonVisibleOverflow() const;
inline bool hasClipPath() const;
inline bool hasHiddenBackface() const;
bool hasViewTransition() const;
bool isViewTransitionPseudo() const;
bool hasOutlineAnnotation() const;
inline bool hasOutline() const;
bool hasSelfPaintingLayer() const;
Expand Down
6 changes: 5 additions & 1 deletion Source/WebCore/rendering/RenderLayer.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -146,6 +146,7 @@
#include "TransformOperationData.h"
#include "TransformationMatrix.h"
#include "TranslateTransformOperation.h"
#include "ViewTransition.h"
#include "WheelEventTestMonitor.h"
#include <stdio.h>
#include <wtf/HexNumber.h>
Expand Down Expand Up @@ -574,6 +575,8 @@ static bool canCreateStackingContext(const RenderLayer& layer)
|| renderer.hasBackdropFilter()
|| renderer.hasBlendMode()
|| renderer.isTransparent()
|| renderer.hasViewTransition()
|| renderer.isViewTransitionPseudo()
|| renderer.isPositioned() // Note that this only creates stacking context in conjunction with explicit z-index.
|| renderer.hasReflection()
|| renderer.style().hasIsolation()
Expand All @@ -599,7 +602,7 @@ bool RenderLayer::shouldBeNormalFlowOnly() const

bool RenderLayer::shouldBeCSSStackingContext() const
{
return !renderer().style().hasAutoUsedZIndex() || renderer().shouldApplyLayoutOrPaintContainment() || isRenderViewLayer();
return !renderer().style().hasAutoUsedZIndex() || renderer().shouldApplyLayoutOrPaintContainment() || renderer().hasViewTransition() || renderer().isViewTransitionPseudo() || isRenderViewLayer();
}

bool RenderLayer::computeCanBeBackdropRoot() const
Expand All @@ -613,6 +616,7 @@ bool RenderLayer::computeCanBeBackdropRoot() const
|| renderer().hasFilter()
|| renderer().hasBlendMode()
|| renderer().hasMask()
|| renderer().hasViewTransition()
|| renderer().isDocumentElementRenderer()
|| (renderer().style().willChange() && renderer().style().willChange()->canBeBackdropRoot());
}
Expand Down
6 changes: 6 additions & 0 deletions Source/WebCore/rendering/RenderLayerBacking.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -1412,6 +1412,12 @@ void RenderLayerBacking::updateGeometry(const RenderLayer* compositedAncestor)

auto primaryLayerPosition = primaryGraphicsLayerRect.location();

// If our content is being used in a view-transition, then all positioning
// is handled using a synthesized 'transform' property on the wrapping
// ::view-transition-new element.
if (renderer().hasViewTransition())
primaryLayerPosition = { };

// FIXME: reflections should force transform-style to be flat in the style: https://bugs.webkit.org/show_bug.cgi?id=106959
bool preserves3D = style.preserves3D() && !renderer().hasReflection();

Expand Down
35 changes: 34 additions & 1 deletion Source/WebCore/rendering/RenderLayerCompositor.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -73,6 +73,7 @@
#include "TiledBacking.h"
#include "TransformState.h"
#include "TranslateTransformOperation.h"
#include "ViewTransition.h"
#include "WillChangeData.h"
#include <wtf/HexNumber.h>
#include <wtf/MemoryPressureHandler.h>
Expand Down Expand Up @@ -1399,6 +1400,28 @@ void RenderLayerCompositor::traverseUnchangedSubtree(RenderLayer* ancestorLayer,
ASSERT(!layer.needsCompositingRequirementsTraversal());
}

void RenderLayerCompositor::collectViewTransitionNewContentLayers(RenderLayer& layer, Vector<Ref<GraphicsLayer>>& childList)
{
if (layer.renderer().style().pseudoElementType() != PseudoId::ViewTransitionNew)
return;

RefPtr activeViewTransition = layer.renderer().document().activeViewTransition();
if (!activeViewTransition)
return;

auto* capturedElement = activeViewTransition->namedElements().find(layer.renderer().style().pseudoElementNameArgument());
if (!capturedElement || !capturedElement->newElement)
return;

auto* capturedRenderer = capturedElement->newElement->renderer();
if (!capturedRenderer || !capturedRenderer->hasLayer())
return;

auto& modelObject = downcast<RenderLayerModelObject>(*capturedRenderer);
if (RenderLayerBacking* backing = modelObject.layer()->backing())
childList.append(*backing->childForSuperlayers());
}

void RenderLayerCompositor::updateBackingAndHierarchy(RenderLayer& layer, Vector<Ref<GraphicsLayer>>& childLayersOfEnclosingLayer, UpdateBackingTraversalState& traversalState, ScrollingTreeState& scrollingTreeState, OptionSet<UpdateLevel> updateLevel)
{
layer.updateDescendantDependentFlags();
Expand Down Expand Up @@ -1520,6 +1543,8 @@ void RenderLayerCompositor::updateBackingAndHierarchy(RenderLayer& layer, Vector
if (auto* renderWidget = dynamicDowncast<RenderWidget>(layer.renderer()))
attachedWidgetContents = attachWidgetContentLayers(*renderWidget);

collectViewTransitionNewContentLayers(layer, childList);

if (!attachedWidgetContents) {
// If the layer has a clipping layer the overflow controls layers will be siblings of the clipping layer.
// Otherwise, the overflow control layers are normal children.
Expand All @@ -1533,7 +1558,8 @@ void RenderLayerCompositor::updateBackingAndHierarchy(RenderLayer& layer, Vector
}
}

childLayersOfEnclosingLayer.append(*layerBacking->childForSuperlayers());
if (!layer.renderer().hasViewTransition())
childLayersOfEnclosingLayer.append(*layerBacking->childForSuperlayers());

if (layerBacking->hasAncestorClippingLayers() && layerBacking->ancestorClippingStack()->hasAnyScrollingLayers())
traversalState.layersClippedByScrollers->append(&layer);
Expand Down Expand Up @@ -2864,6 +2890,7 @@ bool RenderLayerCompositor::requiresCompositingLayer(const RenderLayer& layer, R
|| requiresCompositingForFilters(renderer)
|| requiresCompositingForWillChange(renderer)
|| requiresCompositingForBackfaceVisibility(renderer)
|| requiresCompositingForViewTransition(renderer)
|| requiresCompositingForVideo(renderer)
|| requiresCompositingForModel(renderer)
|| requiresCompositingForFrame(renderer, queryData)
Expand Down Expand Up @@ -2938,6 +2965,7 @@ bool RenderLayerCompositor::requiresOwnBackingStore(const RenderLayer& layer, co
|| requiresCompositingForFilters(renderer)
|| requiresCompositingForWillChange(renderer)
|| requiresCompositingForBackfaceVisibility(renderer)
|| requiresCompositingForViewTransition(renderer)
|| requiresCompositingForVideo(renderer)
|| requiresCompositingForModel(renderer)
|| requiresCompositingForFrame(renderer, queryData)
Expand Down Expand Up @@ -3455,6 +3483,11 @@ bool RenderLayerCompositor::requiresCompositingForBackfaceVisibility(RenderLayer
return false;
}

bool RenderLayerCompositor::requiresCompositingForViewTransition(RenderLayerModelObject& renderer) const
{
return renderer.hasViewTransition() || renderer.isViewTransitionPseudo();
}

bool RenderLayerCompositor::requiresCompositingForVideo(RenderLayerModelObject& renderer) const
{
if (!(m_compositingTriggers & ChromeClient::VideoTrigger))
Expand Down
2 changes: 2 additions & 0 deletions Source/WebCore/rendering/RenderLayerCompositor.h
Original file line number Diff line number Diff line change
Expand Up @@ -304,6 +304,7 @@ class RenderLayerCompositor final : public GraphicsLayerClient {
static RenderLayerCompositor* frameContentsCompositor(RenderWidget&);
// Returns true the widget contents layer was parented.
bool attachWidgetContentLayers(RenderWidget&);
void collectViewTransitionNewContentLayers(RenderLayer&, Vector<Ref<GraphicsLayer>>&);

// Update the geometry of the layers used for clipping and scrolling in frames.
void frameViewDidChangeLocation(const IntPoint& contentsOffset);
Expand Down Expand Up @@ -495,6 +496,7 @@ class RenderLayerCompositor final : public GraphicsLayerClient {
bool requiresCompositingForAnimation(RenderLayerModelObject&) const;
bool requiresCompositingForTransform(RenderLayerModelObject&) const;
bool requiresCompositingForBackfaceVisibility(RenderLayerModelObject&) const;
bool requiresCompositingForViewTransition(RenderLayerModelObject&) const;
bool requiresCompositingForVideo(RenderLayerModelObject&) const;
bool requiresCompositingForCanvas(RenderLayerModelObject&) const;
bool requiresCompositingForFilters(RenderLayerModelObject&) const;
Expand Down
31 changes: 30 additions & 1 deletion Source/WebCore/rendering/RenderViewTransitionCapture.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -36,9 +36,38 @@ RenderViewTransitionCapture::RenderViewTransitionCapture(Type type, Document& do
: RenderReplaced(type, document, WTFMove(style), { }, ReplacedFlag::IsViewTransitionCapture)
{ }

void RenderViewTransitionCapture::setImage(const LayoutSize& size)
void RenderViewTransitionCapture::setImage(RefPtr<ImageBuffer> oldImage, const LayoutSize& size)
{
setIntrinsicSize(size);
m_oldImage = oldImage;
}

void RenderViewTransitionCapture::paintReplaced(PaintInfo& paintInfo, const LayoutPoint& paintOffset)
{
auto& context = paintInfo.context();

LayoutRect contentBoxRect = this->contentBoxRect();

if (context.detectingContentfulPaint())
return;

contentBoxRect.moveBy(paintOffset);
LayoutRect replacedContentRect = this->replacedContentRect();
replacedContentRect.moveBy(paintOffset);

// Not allowed to overflow the content box.
bool clip = !contentBoxRect.contains(replacedContentRect);
GraphicsContextStateSaver stateSaver(paintInfo.context(), clip);
if (clip)
paintInfo.context().clip(snappedIntRect(contentBoxRect));

if (paintInfo.phase == PaintPhase::Foreground)
page().addRelevantRepaintedObject(*this, intersection(replacedContentRect, contentBoxRect));

InterpolationQualityMaintainer interpolationMaintainer(context, ImageQualityController::interpolationQualityFromStyle(style()));
if (m_oldImage)
context.drawImageBuffer(*m_oldImage, snappedIntRect(replacedContentRect), { context.compositeOperation() });

}

} // namespace WebCore
7 changes: 4 additions & 3 deletions Source/WebCore/rendering/RenderViewTransitionCapture.h
Original file line number Diff line number Diff line change
Expand Up @@ -34,13 +34,14 @@ class RenderViewTransitionCapture : public RenderReplaced {
public:
RenderViewTransitionCapture(Type, Document&, RenderStyle&&);

// FIXME: This should be setting the actual image, as well as the instrinsic
// size
void setImage(const LayoutSize&);
void setImage(RefPtr<ImageBuffer>, const LayoutSize&);

void paintReplaced(PaintInfo&, const LayoutPoint& paintOffset) override;

private:
ASCIILiteral renderName() const override { return style().pseudoElementType() == PseudoId::ViewTransitionNew ? "RenderViewTransitionNew"_s : "RenderViewTransitionOld"_s; }

RefPtr<ImageBuffer> m_oldImage;
};

} // namespace WebCore
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -81,7 +81,7 @@ void RenderTreeUpdater::ViewTransition::updatePseudoElementTree(RenderElement& d
newViewTransitionRoot->initializeStyle();
documentElementRenderer.view().setViewTransitionRoot(*newViewTransitionRoot.get());
viewTransitionRoot = newViewTransitionRoot.get();
m_updater.m_builder.attach(documentElementRenderer, WTFMove(newViewTransitionRoot));
m_updater.m_builder.attach(*documentElementRenderer.parent(), WTFMove(newViewTransitionRoot));
}

// No groups. The map is constant during the duration of the transition, so we don't need to handle deletions.
Expand All @@ -103,6 +103,7 @@ void RenderTreeUpdater::ViewTransition::updatePseudoElementTree(RenderElement& d
buildPseudoElementGroup(name, documentElementRenderer, currentGroup);
currentGroup = currentGroup ? currentGroup->previousSibling() : documentElementRenderer.view().viewTransitionRoot()->firstChild();
}

currentGroup = currentGroup ? currentGroup->nextSibling() : nullptr;
}

Expand All @@ -126,7 +127,7 @@ void RenderTreeUpdater::ViewTransition::buildPseudoElementGroup(const AtomString
RenderPtr<RenderViewTransitionCapture> rendererViewTransition = WebCore::createRenderer<RenderViewTransitionCapture>(RenderObject::Type::ViewTransitionCapture, document, WTFMove(newStyle));

if (const auto* capturedElement = document->activeViewTransition()->namedElements().find(name))
rendererViewTransition->setImage(capturedElement->oldSize);
rendererViewTransition->setImage(pseudoId == PseudoId::ViewTransitionOld ? capturedElement->oldImage : nullptr, capturedElement->oldSize);

renderer = WTFMove(rendererViewTransition);
} else
Expand Down Expand Up @@ -172,7 +173,7 @@ void RenderTreeUpdater::ViewTransition::updatePseudoElementGroup(const RenderSty
RenderPtr<RenderViewTransitionCapture> rendererViewTransition = WebCore::createRenderer<RenderViewTransitionCapture>(RenderObject::Type::ViewTransitionCapture, documentElementRenderer.document(), WTFMove(newStyle));

if (const auto* capturedElement = documentElementRenderer.document().activeViewTransition()->namedElements().find(name))
rendererViewTransition->setImage(capturedElement->oldSize);
rendererViewTransition->setImage(pseudoId == PseudoId::ViewTransitionOld ? capturedElement->oldImage : nullptr, capturedElement->oldSize);

renderer = WTFMove(rendererViewTransition);
} else
Expand Down

0 comments on commit 115f020

Please sign in to comment.