diff --git a/dom/view-transitions/ViewTransition.cpp b/dom/view-transitions/ViewTransition.cpp index 15bc9b432fdb..dd35e2aa871b 100644 --- a/dom/view-transitions/ViewTransition.cpp +++ b/dom/view-transitions/ViewTransition.cpp @@ -5,11 +5,17 @@ #include "ViewTransition.h" #include "mozilla/gfx/2D.h" +#include "WindowRenderer.h" +#include "mozilla/layers/WebRenderLayerManager.h" +#include "mozilla/layers/RenderRootStateManager.h" #include "mozilla/dom/BindContext.h" +#include "mozilla/layers/WebRenderBridgeChild.h" +#include "mozilla/gfx/DataSurfaceHelpers.h" #include "mozilla/dom/DocumentInlines.h" #include "mozilla/dom/DocumentTimeline.h" #include "mozilla/dom/Promise-inl.h" #include "mozilla/dom/ViewTransitionBinding.h" +#include "mozilla/image/WebRenderImageProvider.h" #include "mozilla/webrender/WebRenderAPI.h" #include "mozilla/AnimationEventDispatcher.h" #include "mozilla/EffectSet.h" @@ -18,9 +24,11 @@ #include "mozilla/SVGIntegrationUtils.h" #include "mozilla/WritingModes.h" #include "nsDisplayList.h" +#include "nsFrameState.h" #include "nsITimer.h" #include "nsLayoutUtils.h" #include "nsPresContext.h" +#include "nsString.h" #include "Units.h" namespace mozilla::dom { @@ -84,8 +92,78 @@ static RefPtr CaptureFallbackSnapshot( return surf->GetDataSurface(); } +static layers::RenderRootStateManager* GetWRStateManagerFor(nsIFrame* aFrame) { + if ((true)) { + // TODO(emilio): Enable this code-path. + return nullptr; + } + nsIWidget* widget = aFrame->GetNearestWidget(); + if (NS_WARN_IF(!widget)) { + return nullptr; + } + auto* renderer = widget->GetWindowRenderer(); + if (NS_WARN_IF(!renderer)) { + return nullptr; + } + layers::WebRenderLayerManager* lm = renderer->AsWebRender(); + if (NS_WARN_IF(!lm)) { + return nullptr; + } + return lm->GetRenderRootStateManager(); +} + +static constexpr wr::ImageKey kNoKey{{0}, 0}; + +struct OldSnapshotData { + wr::ImageKey mImageKey = kNoKey; + nsSize mSize; + RefPtr mFallback; + RefPtr mManager; + + OldSnapshotData() = default; + + explicit OldSnapshotData(nsIFrame* aFrame) + : mSize(aFrame->InkOverflowRectRelativeToSelf().Size()), + mManager(GetWRStateManagerFor(aFrame)) { + if (mManager) { + mImageKey = mManager->WrBridge()->GetNextImageKey(); + } else { + mFallback = CaptureFallbackSnapshot(aFrame); + } + } + + void EnsureKey(layers::RenderRootStateManager* aManager, + wr::IpcResourceUpdateQueue& aResources) { + if (mImageKey != kNoKey) { + MOZ_ASSERT(mManager == aManager, "Stale manager?"); + return; + } + if (NS_WARN_IF(!mFallback)) { + return; + } + gfx::DataSourceSurface::ScopedMap map(mFallback, + gfx::DataSourceSurface::READ); + if (NS_WARN_IF(!map.IsMapped())) { + return; + } + mManager = aManager; + mImageKey = aManager->WrBridge()->GetNextImageKey(); + auto size = mFallback->GetSize(); + auto format = mFallback->GetFormat(); + wr::ImageDescriptor desc(size, format); + Range bytes(map.GetData(), map.GetStride() * size.height); + Unused << NS_WARN_IF(!aResources.AddImage(mImageKey, desc, bytes)); + } + + ~OldSnapshotData() { + if (mManager) { + mManager->AddImageKeyForDiscard(mImageKey); + } + } +}; + struct CapturedElementOldState { - RefPtr mImage; + OldSnapshotData mSnapshot; // Whether we tried to capture an image. Note we might fail to get a // snapshot, so this might not be the same as !!mImage. bool mTriedImage = false; @@ -101,7 +179,7 @@ struct CapturedElementOldState { CapturedElementOldState(nsIFrame* aFrame, const nsSize& aSnapshotContainingBlockSize) - : mImage(CaptureFallbackSnapshot(aFrame)), + : mSnapshot(aFrame), mTriedImage(true), mSize(aFrame->Style()->IsRootElementStyle() ? aSnapshotContainingBlockSize @@ -119,6 +197,8 @@ struct CapturedElementOldState { struct ViewTransition::CapturedElement { CapturedElementOldState mOldState; RefPtr mNewElement; + wr::SnapshotImageKey mNewSnapshotKey{kNoKey}; + nsSize mNewSnapshotSize; CapturedElement() = default; @@ -135,6 +215,14 @@ struct ViewTransition::CapturedElement { RefPtr mOldRule; // The rules for ::view-transition-new(). RefPtr mNewRule; + + ~CapturedElement() { + if (wr::AsImageKey(mNewSnapshotKey) != kNoKey) { + MOZ_ASSERT(mOldState.mSnapshot.mManager); + mOldState.mSnapshot.mManager->AddSnapshotImageKeyForDiscard( + mNewSnapshotKey); + } + } }; static inline void ImplCycleCollectionTraverse( @@ -164,12 +252,75 @@ ViewTransition::ViewTransition(Document& aDoc, ViewTransition::~ViewTransition() { ClearTimeoutTimer(); } -gfx::DataSourceSurface* ViewTransition::GetOldSurface(nsAtom* aName) const { +Maybe ViewTransition::GetOldSize(nsAtom* aName) const { + auto* el = mNamedElements.Get(aName); + if (NS_WARN_IF(!el)) { + return {}; + } + return Some(el->mOldState.mSnapshot.mSize); +} + +Maybe ViewTransition::GetNewSize(nsAtom* aName) const { + auto* el = mNamedElements.Get(aName); + if (NS_WARN_IF(!el)) { + return {}; + } + return Some(el->mNewSnapshotSize); +} + +const wr::ImageKey* ViewTransition::GetOldImageKey( + nsAtom* aName, layers::RenderRootStateManager* aManager, + wr::IpcResourceUpdateQueue& aResources) const { auto* el = mNamedElements.Get(aName); if (NS_WARN_IF(!el)) { return nullptr; } - return el->mOldState.mImage; + el->mOldState.mSnapshot.EnsureKey(aManager, aResources); + return &el->mOldState.mSnapshot.mImageKey; +} + +const wr::ImageKey* ViewTransition::GetNewImageKey(nsAtom* aName) const { + auto* el = mNamedElements.Get(aName); + if (NS_WARN_IF(!el)) { + return nullptr; + } + return &el->mNewSnapshotKey._0; +} + +const wr::ImageKey* ViewTransition::GetImageKeyForCapturedFrame( + nsIFrame* aFrame, layers::RenderRootStateManager* aManager, + wr::IpcResourceUpdateQueue& aResources) const { + MOZ_ASSERT(aFrame); + MOZ_ASSERT(aFrame->HasAnyStateBits(NS_FRAME_CAPTURED_IN_VIEW_TRANSITION)); + + if (!StaticPrefs::dom_viewTransitions_live_capture()) { + return nullptr; + } + + nsAtom* name = aFrame->StyleUIReset()->mViewTransitionName._0.AsAtom(); + if (NS_WARN_IF(name->IsEmpty())) { + return nullptr; + } + const bool isOld = mPhase < Phase::Animating; + if (isOld) { + return GetOldImageKey(name, aManager, aResources); + } + auto* el = mNamedElements.Get(name); + if (NS_WARN_IF(!el)) { + return nullptr; + } + if (NS_WARN_IF(el->mNewElement != aFrame->GetContent())) { + return nullptr; + } + if (wr::AsImageKey(el->mNewSnapshotKey) == kNoKey) { + MOZ_ASSERT(!el->mOldState.mSnapshot.mManager || + el->mOldState.mSnapshot.mManager == aManager, + "Stale manager?"); + el->mNewSnapshotKey = {aManager->WrBridge()->GetNextImageKey()}; + el->mOldState.mSnapshot.mManager = aManager; + aResources.AddSnapshotImage(el->mNewSnapshotKey); + } + return &el->mNewSnapshotKey._0; } nsIGlobalObject* ViewTransition::GetParentObject() const { @@ -262,7 +413,7 @@ void ViewTransition::CallUpdateCallback(ErrorResult& aRv) { // new state, so we need to flush frames. Do it here so that we deal // with other potential script execution skipping the transition or // what not in a consistent way. - aVt->mDocument->FlushPendingNotifications(FlushType::Frames); + aVt->mDocument->FlushPendingNotifications(FlushType::Layout); if (aVt->mPhase == Phase::Done) { // "Skip a transition" step 8. We need to resolve "finished" after // update-callback-done. @@ -604,15 +755,15 @@ bool ViewTransition::UpdatePseudoElementStyles(bool aNeedsInvalidation) { auto size = CSSPixel::FromAppUnits(newRect); // NOTE(emilio): Intentionally not short-circuiting. Int cast is needed to // silence warning. - bool changed = int(SetProp(rule, mDocument, eCSSProperty_width, size.width, - eCSSUnit_Pixel)) | - SetProp(rule, mDocument, eCSSProperty_height, size.height, - eCSSUnit_Pixel) | - SetProp(rule, mDocument, eCSSProperty_transform, - EffectiveTransform(frame)); + bool groupStyleChanged = int(SetProp(rule, mDocument, eCSSProperty_width, + size.width, eCSSUnit_Pixel)) | + SetProp(rule, mDocument, eCSSProperty_height, + size.height, eCSSUnit_Pixel) | + SetProp(rule, mDocument, eCSSProperty_transform, + EffectiveTransform(frame)); // TODO: writing-mode, direction, text-orientation, mix-blend-mode, // backdrop-filter, color-scheme. - if (changed && aNeedsInvalidation) { + if (groupStyleChanged && aNeedsInvalidation) { auto* pseudo = FindPseudo(PseudoStyleRequest( PseudoStyleType::viewTransitionGroup, transitionName)); MOZ_ASSERT(pseudo); @@ -621,7 +772,16 @@ bool ViewTransition::UpdatePseudoElementStyles(bool aNeedsInvalidation) { nsLayoutUtils::PostRestyleEvent(pseudo, RestyleHint::RECASCADE_SELF, nsChangeHint(0)); } - // 5. TODO(emilio): Live capturing (probably nothing to do here) + + // 5. Live capturing (nothing to do here regarding the capture itself, but + // if the size has changed, then we need to invalidate the new frame). + auto oldSize = capturedElement.mNewSnapshotSize; + capturedElement.mNewSnapshotSize = + frame->InkOverflowRectRelativeToSelf().Size(); + if (oldSize != capturedElement.mNewSnapshotSize && aNeedsInvalidation) { + frame->PresShell()->FrameNeedsReflow( + frame, IntrinsicDirty::FrameAndAncestors, NS_FRAME_IS_DIRTY); + } } return true; } @@ -931,6 +1091,8 @@ Maybe ViewTransition::CaptureNewState() { auto& capturedElement = mNamedElements.LookupOrInsertWith( name, [&] { return MakeUnique(); }); capturedElement->mNewElement = aFrame->GetContent()->AsElement(); + capturedElement->mNewSnapshotSize = + aFrame->InkOverflowRectRelativeToSelf().Size(); aFrame->AddStateBits(NS_FRAME_CAPTURED_IN_VIEW_TRANSITION); return true; }); diff --git a/dom/view-transitions/ViewTransition.h b/dom/view-transitions/ViewTransition.h index 0481053b09fa..84d8abdab527 100644 --- a/dom/view-transitions/ViewTransition.h +++ b/dom/view-transitions/ViewTransition.h @@ -5,6 +5,7 @@ #ifndef mozilla_dom_ViewTransition_h #define mozilla_dom_ViewTransition_h +#include "mozilla/layers/IpcResourceUpdateQueue.h" #include "nsRect.h" #include "nsWrapperCache.h" #include "nsAtomHashKeys.h" @@ -25,6 +26,15 @@ namespace gfx { class DataSourceSurface; } +namespace layers { +class RenderRootStateManager; +} + +namespace wr { +struct ImageKey; +class IpcResourceUpdateQueue; +} // namespace wr + namespace dom { class Document; @@ -69,7 +79,15 @@ class ViewTransition final : public nsISupports, public nsWrapperCache { void PerformPendingOperations(); Element* GetRoot() const { return mViewTransitionRoot; } - gfx::DataSourceSurface* GetOldSurface(nsAtom* aName) const; + Maybe GetOldSize(nsAtom* aName) const; + Maybe GetNewSize(nsAtom* aName) const; + const wr::ImageKey* GetOldImageKey(nsAtom* aName, + layers::RenderRootStateManager*, + wr::IpcResourceUpdateQueue&) const; + const wr::ImageKey* GetNewImageKey(nsAtom* aName) const; + const wr::ImageKey* GetImageKeyForCapturedFrame( + nsIFrame* aFrame, layers::RenderRootStateManager*, + wr::IpcResourceUpdateQueue&) const; Element* FindPseudo(const PseudoStyleRequest&) const; diff --git a/gfx/layers/ipc/WebRenderMessages.ipdlh b/gfx/layers/ipc/WebRenderMessages.ipdlh index 469f9a4c98f4..1689a0322429 100644 --- a/gfx/layers/ipc/WebRenderMessages.ipdlh +++ b/gfx/layers/ipc/WebRenderMessages.ipdlh @@ -25,6 +25,7 @@ using mozilla::wr::FontInstanceKey from "mozilla/webrender/WebRenderTypes.h"; using mozilla::wr::FontKey from "mozilla/webrender/WebRenderTypes.h"; using mozilla::wr::ImageKey from "mozilla/webrender/WebRenderTypes.h"; using mozilla::wr::BlobImageKey from "mozilla/webrender/WebRenderTypes.h"; +using mozilla::wr::SnapshotImageKey from "mozilla/webrender/WebRenderTypes.h"; using mozilla::wr::WrRotation from "mozilla/webrender/WebRenderTypes.h"; using mozilla::wr::PipelineId from "mozilla/webrender/WebRenderTypes.h"; using mozilla::LayoutDeviceRect from "Units.h"; @@ -114,6 +115,10 @@ struct OpAddBlobImage { BlobImageKey key; }; +struct OpAddSnapshotImage { + SnapshotImageKey key; +}; + struct OpUpdateImage { ImageDescriptor descriptor; OffsetRange bytes; @@ -143,6 +148,10 @@ struct OpDeleteImage { ImageKey key; }; +struct OpDeleteSnapshotImage { + SnapshotImageKey key; +}; + struct OpDeleteBlobImage { BlobImageKey key; }; @@ -179,11 +188,13 @@ struct OpDeleteFontInstance { union OpUpdateResource { OpAddImage; OpAddBlobImage; + OpAddSnapshotImage; OpUpdateImage; OpUpdateBlobImage; OpSetBlobImageVisibleArea; OpDeleteImage; OpDeleteBlobImage; + OpDeleteSnapshotImage; OpAddRawFont; OpAddFontDescriptor; OpDeleteFont; diff --git a/gfx/layers/wr/IpcResourceUpdateQueue.cpp b/gfx/layers/wr/IpcResourceUpdateQueue.cpp index 470649882e8f..1c04ba7ea6d6 100644 --- a/gfx/layers/wr/IpcResourceUpdateQueue.cpp +++ b/gfx/layers/wr/IpcResourceUpdateQueue.cpp @@ -322,6 +322,10 @@ bool IpcResourceUpdateQueue::AddImage(ImageKey key, return true; } +void IpcResourceUpdateQueue::AddSnapshotImage(SnapshotImageKey aKey) { + mUpdates.AppendElement(layers::OpAddSnapshotImage(aKey)); +} + bool IpcResourceUpdateQueue::AddBlobImage(BlobImageKey key, const ImageDescriptor& aDescriptor, Range aBytes, @@ -393,6 +397,10 @@ void IpcResourceUpdateQueue::DeleteImage(ImageKey aKey) { mUpdates.AppendElement(layers::OpDeleteImage(aKey)); } +void IpcResourceUpdateQueue::DeleteSnapshotImage(SnapshotImageKey aKey) { + mUpdates.AppendElement(layers::OpDeleteSnapshotImage(aKey)); +} + void IpcResourceUpdateQueue::DeleteBlobImage(BlobImageKey aKey) { mUpdates.AppendElement(layers::OpDeleteBlobImage(aKey)); } diff --git a/gfx/layers/wr/IpcResourceUpdateQueue.h b/gfx/layers/wr/IpcResourceUpdateQueue.h index 6096ddbddb38..b33b55182da3 100644 --- a/gfx/layers/wr/IpcResourceUpdateQueue.h +++ b/gfx/layers/wr/IpcResourceUpdateQueue.h @@ -132,6 +132,8 @@ class IpcResourceUpdateQueue { bool AddBlobImage(wr::BlobImageKey aKey, const ImageDescriptor& aDescriptor, Range aBytes, ImageIntRect aVisibleRect); + void AddSnapshotImage(wr::SnapshotImageKey aKey); + void AddSharedExternalImage(wr::ExternalImageId aExtId, wr::ImageKey aKey); void PushExternalImageForTexture(wr::ExternalImageId aExtId, @@ -156,6 +158,8 @@ class IpcResourceUpdateQueue { void DeleteBlobImage(wr::BlobImageKey aKey); + void DeleteSnapshotImage(wr::SnapshotImageKey aKey); + bool AddRawFont(wr::FontKey aKey, Range aBytes, uint32_t aIndex); bool AddFontDescriptor(wr::FontKey aKey, Range aBytes, diff --git a/gfx/layers/wr/RenderRootStateManager.cpp b/gfx/layers/wr/RenderRootStateManager.cpp index 8ddaa601a962..c5f8f044269f 100644 --- a/gfx/layers/wr/RenderRootStateManager.cpp +++ b/gfx/layers/wr/RenderRootStateManager.cpp @@ -86,6 +86,11 @@ void RenderRootStateManager::AddBlobImageKeyForDiscard(wr::BlobImageKey key) { mBlobImageKeysToDelete.AppendElement(key); } +void RenderRootStateManager::AddSnapshotImageKeyForDiscard( + wr::SnapshotImageKey key) { + mSnapshotImageKeysToDelete.AppendElement(key); +} + void RenderRootStateManager::DiscardImagesInTransaction( wr::IpcResourceUpdateQueue& aResources) { for (const auto& key : mImageKeysToDelete) { @@ -94,8 +99,12 @@ void RenderRootStateManager::DiscardImagesInTransaction( for (const auto& key : mBlobImageKeysToDelete) { aResources.DeleteBlobImage(key); } + for (const auto& key : mSnapshotImageKeysToDelete) { + aResources.DeleteSnapshotImage(key); + } mImageKeysToDelete.Clear(); mBlobImageKeysToDelete.Clear(); + mSnapshotImageKeysToDelete.Clear(); } void RenderRootStateManager::DiscardLocalImages() { @@ -104,6 +113,7 @@ void RenderRootStateManager::DiscardLocalImages() { // image keys but didn't tell the parent about them yet. mImageKeysToDelete.Clear(); mBlobImageKeysToDelete.Clear(); + mSnapshotImageKeysToDelete.Clear(); } void RenderRootStateManager::ClearCachedResources() { diff --git a/gfx/layers/wr/RenderRootStateManager.h b/gfx/layers/wr/RenderRootStateManager.h index 6c0221776e93..f44e4abb2713 100644 --- a/gfx/layers/wr/RenderRootStateManager.h +++ b/gfx/layers/wr/RenderRootStateManager.h @@ -35,8 +35,9 @@ class RenderRootStateManager { WebRenderUserDataRefTable* GetWebRenderUserDataTable(); WebRenderLayerManager* LayerManager() { return mLayerManager; } - void AddImageKeyForDiscard(wr::ImageKey key); - void AddBlobImageKeyForDiscard(wr::BlobImageKey key); + void AddImageKeyForDiscard(wr::ImageKey); + void AddBlobImageKeyForDiscard(wr::BlobImageKey); + void AddSnapshotImageKeyForDiscard(wr::SnapshotImageKey); void DiscardImagesInTransaction(wr::IpcResourceUpdateQueue& aResources); void DiscardLocalImages(); @@ -76,6 +77,7 @@ class RenderRootStateManager { Maybe mAsyncResourceUpdates; nsTArray mImageKeysToDelete; nsTArray mBlobImageKeysToDelete; + nsTArray mSnapshotImageKeysToDelete; std::unordered_map> mAsyncAnimations; diff --git a/gfx/layers/wr/WebRenderBridgeParent.cpp b/gfx/layers/wr/WebRenderBridgeParent.cpp index 03404b9e7bf9..114582413b5c 100644 --- a/gfx/layers/wr/WebRenderBridgeParent.cpp +++ b/gfx/layers/wr/WebRenderBridgeParent.cpp @@ -607,6 +607,24 @@ bool WebRenderBridgeParent::UpdateResources( wr::ToDeviceIntRect(op.area())); break; } + case OpUpdateResource::TOpAddSnapshotImage: { + const auto& op = cmd.get_OpAddSnapshotImage(); + if (!MatchesNamespace(wr::AsImageKey(op.key()))) { + MOZ_ASSERT_UNREACHABLE("Stale snapshot image key (add)!"); + break; + } + aUpdates.AddSnapshotImage(op.key()); + break; + } + case OpUpdateResource::TOpDeleteSnapshotImage: { + const auto& op = cmd.get_OpDeleteSnapshotImage(); + if (!MatchesNamespace(wr::AsImageKey(op.key()))) { + MOZ_ASSERT_UNREACHABLE("Stale snapshot image key (remove)!"); + break; + } + aUpdates.DeleteSnapshotImage(op.key()); + break; + } case OpUpdateResource::TOpAddSharedExternalImage: { const auto& op = cmd.get_OpAddSharedExternalImage(); // gfxCriticalNote is called on error diff --git a/gfx/layers/wr/WebRenderBridgeParent.h b/gfx/layers/wr/WebRenderBridgeParent.h index 5721734274f0..20a27122951f 100644 --- a/gfx/layers/wr/WebRenderBridgeParent.h +++ b/gfx/layers/wr/WebRenderBridgeParent.h @@ -236,6 +236,10 @@ class WebRenderBridgeParent final : public PWebRenderBridgeParent, return MatchesNamespace(wr::AsImageKey(aBlobKey)); } + bool MatchesNamespace(const wr::SnapshotImageKey& aSnapshotKey) const { + return MatchesNamespace(wr::AsImageKey(aSnapshotKey)); + } + bool MatchesNamespace(const wr::FontKey& aFontKey) const { return aFontKey.mNamespace == mIdNamespace; } diff --git a/gfx/layers/wr/WebRenderMessageUtils.h b/gfx/layers/wr/WebRenderMessageUtils.h index 5384ffe85ad2..a06863957c3c 100644 --- a/gfx/layers/wr/WebRenderMessageUtils.h +++ b/gfx/layers/wr/WebRenderMessageUtils.h @@ -144,6 +144,12 @@ inline auto TiedFields( return std::tie(a._0); } +template <> +inline auto TiedFields( + mozilla::wr::SnapshotImageKey& a) { + return std::tie(a._0); +} + template <> inline auto TiedFields(mozilla::wr::FontKey& a) { return std::tie(a.mNamespace, a.mHandle); @@ -340,6 +346,10 @@ template <> struct ParamTraits : public ParamTraits_TiedFields {}; +template <> +struct ParamTraits + : public ParamTraits_TiedFields {}; + template <> struct ParamTraits : public ParamTraits_TiedFields {}; diff --git a/gfx/webrender_bindings/webrender_ffi.h b/gfx/webrender_bindings/webrender_ffi.h index f3004532909a..85393beaeb19 100644 --- a/gfx/webrender_bindings/webrender_ffi.h +++ b/gfx/webrender_bindings/webrender_ffi.h @@ -113,9 +113,10 @@ template struct mozilla::wr::Box2D; namespace mozilla { namespace wr { -// Cast a blob image key into a regular image for use in -// a display item. +// Cast a blob image key into a regular image for use in a display item. inline ImageKey AsImageKey(BlobImageKey aKey) { return aKey._0; } +// Cast a snapshot image key into a regular image for use in a display item. +inline ImageKey AsImageKey(SnapshotImageKey aKey) { return aKey._0; } } // namespace wr } // namespace mozilla diff --git a/layout/base/nsCSSFrameConstructor.cpp b/layout/base/nsCSSFrameConstructor.cpp index ff0d637c24ae..7421545fbcb0 100644 --- a/layout/base/nsCSSFrameConstructor.cpp +++ b/layout/base/nsCSSFrameConstructor.cpp @@ -221,7 +221,7 @@ nsIFrame* NS_NewXULImageFrame(PresShell*, ComputedStyle*); nsIFrame* NS_NewImageFrameForContentProperty(PresShell*, ComputedStyle*); nsIFrame* NS_NewImageFrameForGeneratedContentIndex(PresShell*, ComputedStyle*); nsIFrame* NS_NewImageFrameForListStyleImage(PresShell*, ComputedStyle*); -nsIFrame* NS_NewImageFrameForViewTransitionOld(PresShell*, ComputedStyle*); +nsIFrame* NS_NewImageFrameForViewTransition(PresShell*, ComputedStyle*); // Returns true if aFrame is an anonymous flex/grid item. static inline bool IsAnonymousItem(const nsIFrame* aFrame) { @@ -3493,10 +3493,11 @@ nsCSSFrameConstructor::FindHTMLData(const Element& aElement, return &sComboboxLabelData; } } - if (aElement.GetPseudoElementType() == PseudoStyleType::viewTransitionOld) { - static constexpr FrameConstructionData sViewTransitionOldData( - NS_NewImageFrameForViewTransitionOld); - return &sViewTransitionOldData; + if (aStyle.GetPseudoType() == PseudoStyleType::viewTransitionOld || + aStyle.GetPseudoType() == PseudoStyleType::viewTransitionNew) { + static constexpr FrameConstructionData sViewTransitionData( + NS_NewImageFrameForViewTransition); + return &sViewTransitionData; } } diff --git a/layout/generic/nsIFrame.cpp b/layout/generic/nsIFrame.cpp index 01f724b0c429..adfa2ebff5cd 100644 --- a/layout/generic/nsIFrame.cpp +++ b/layout/generic/nsIFrame.cpp @@ -20,6 +20,7 @@ #include "mozilla/DisplayPortUtils.h" #include "mozilla/EventForwards.h" #include "mozilla/FocusModel.h" +#include "mozilla/StaticPrefs_dom.h" #include "mozilla/dom/CSSAnimation.h" #include "mozilla/dom/CSSTransition.h" #include "mozilla/dom/ContentVisibilityAutoStateChangeEvent.h" @@ -3366,16 +3367,11 @@ void nsIFrame::BuildDisplayListForStackingContext( // outside to inside. enum class ContainerItemType : uint8_t { None = 0, - OwnLayerIfNeeded, - BlendMode, FixedPosition, OwnLayerForTransformWithRoundedClip, Perspective, Transform, - SeparatorTransforms, - Opacity, Filter, - BlendContainer }; nsDisplayListBuilder::AutoContainerASRTracker contASRTracker(aBuilder); @@ -3579,6 +3575,18 @@ void nsIFrame::BuildDisplayListForStackingContext( createdContainer = true; } + // FIXME: Ensure this is the right place to do this. + if (HasAnyStateBits(NS_FRAME_CAPTURED_IN_VIEW_TRANSITION) && + StaticPrefs::dom_viewTransitions_live_capture()) { + resultList.AppendNewToTopWithIndex( + aBuilder, this, + /* aIndex = */ nsDisplayOwnLayer::OwnLayerForViewTransitionCapture, + &resultList, containerItemASR, nsDisplayOwnLayerFlags::None, + ScrollbarData{}, + /* aForceActive = */ false, false); + createdContainer = true; + } + // If there are any SVG effects, wrap the list up in an SVG effects item // (which also handles CSS group opacity). Note that we create an SVG effects // item even if resultList is empty, since a filter can produce graphical diff --git a/layout/generic/nsImageFrame.cpp b/layout/generic/nsImageFrame.cpp index 216ee22b7d1c..d52da8d827c8 100644 --- a/layout/generic/nsImageFrame.cpp +++ b/layout/generic/nsImageFrame.cpp @@ -107,6 +107,8 @@ using namespace mozilla::layers; using mozilla::layout::TextDrawTarget; +static constexpr wr::ImageKey kNoKey{{0}, 0}; + class nsDisplayGradient final : public nsPaintedDisplayItem { public: nsDisplayGradient(nsDisplayListBuilder* aBuilder, nsImageFrame* aFrame) @@ -399,10 +401,10 @@ nsIFrame* NS_NewImageFrameForListStyleImage(PresShell* aPresShell, nsImageFrame::Kind::ListStyleImage); } -nsIFrame* NS_NewImageFrameForViewTransitionOld(PresShell* aPresShell, - ComputedStyle* aStyle) { +nsIFrame* NS_NewImageFrameForViewTransition(PresShell* aPresShell, + ComputedStyle* aStyle) { return new (aPresShell) nsImageFrame(aStyle, aPresShell->GetPresContext(), - nsImageFrame::Kind::ViewTransitionOld); + nsImageFrame::Kind::ViewTransition); } bool nsImageFrame::ShouldShowBrokenImageIcon() const { @@ -471,7 +473,7 @@ a11y::AccType nsImageFrame::AccessibleType() { return a11y::eNoType; } - if (mKind == Kind::ViewTransitionOld) { + if (mKind == Kind::ViewTransition) { // View transitions don't show up in the a11y tree. return a11y::eNoType; } @@ -538,13 +540,6 @@ void nsImageFrame::Destroy(DestroyContext& aContext) { BrokenImageIcon::RemoveObserver(this); } - if (mViewTransitionData.HasKey()) { - MOZ_ASSERT(mViewTransitionData.mManager); - mViewTransitionData.mManager->AddImageKeyForDiscard( - mViewTransitionData.mImageKey); - mViewTransitionData = {}; - } - nsAtomicContainerFrame::Destroy(aContext); } @@ -630,7 +625,7 @@ static bool SizeIsAvailable(imgIRequest* aRequest) { const StyleImage* nsImageFrame::GetImageFromStyle() const { switch (mKind) { - case Kind::ViewTransitionOld: + case Kind::ViewTransition: break; case Kind::ImageLoadingContent: break; @@ -750,7 +745,7 @@ void nsImageFrame::Init(nsIContent* aContent, nsContainerFrame* aParent, } } else if (mKind == Kind::XULImage) { UpdateXULImage(); - } else if (mKind == Kind::ViewTransitionOld) { + } else if (mKind == Kind::ViewTransition) { // View transitions have a surface directly. } else { const StyleImage* image = GetImageFromStyle(); @@ -881,13 +876,10 @@ IntrinsicSize nsImageFrame::ComputeIntrinsicSize( return FinishIntrinsicSize(containAxes, intrinsicSize); } - if (auto* surf = GetViewTransitionSurface()) { + if (auto size = GetViewTransitionSnapshotSize()) { IntrinsicSize intrinsicSize; - auto devPx = LayoutDeviceIntSize::FromUnknownSize(surf->GetSize()); - auto size = LayoutDeviceIntSize::ToAppUnits( - devPx, PresContext()->AppUnitsPerDevPixel()); - intrinsicSize.width.emplace(size.width); - intrinsicSize.height.emplace(size.height); + intrinsicSize.width.emplace(size->width); + intrinsicSize.height.emplace(size->height); return FinishIntrinsicSize(containAxes, intrinsicSize); } @@ -950,18 +942,47 @@ bool nsImageFrame::UpdateIntrinsicSize() { return mIntrinsicSize != oldIntrinsicSize; } -gfx::DataSourceSurface* nsImageFrame::GetViewTransitionSurface() const { - if (mKind != Kind::ViewTransitionOld) { - return nullptr; - } - auto* vt = PresContext()->Document()->GetActiveViewTransition(); - if (NS_WARN_IF(!vt)) { +nsAtom* nsImageFrame::GetViewTransitionName() const { + if (mKind != Kind::ViewTransition) { return nullptr; } MOZ_ASSERT(GetContent()->AsElement()->HasName()); - nsAtom* name = - GetContent()->AsElement()->GetParsedAttr(nsGkAtoms::name)->GetAtomValue(); - return vt->GetOldSurface(name); + return GetContent() + ->AsElement() + ->GetParsedAttr(nsGkAtoms::name) + ->GetAtomValue(); +} + +Maybe nsImageFrame::GetViewTransitionSnapshotSize() const { + auto* name = GetViewTransitionName(); + if (!name) { + return {}; + } + auto* vt = PresContext()->Document()->GetActiveViewTransition(); + if (NS_WARN_IF(!vt)) { + return {}; + } + return Style()->GetPseudoType() == PseudoStyleType::viewTransitionOld + ? vt->GetOldSize(name) + : vt->GetNewSize(name); +} + +wr::ImageKey nsImageFrame::GetViewTransitionImageKey( + layers::RenderRootStateManager* aManager, + wr::IpcResourceUpdateQueue& aResources) const { + auto* name = GetViewTransitionName(); + if (!name) { + return kNoKey; + } + auto* vt = PresContext()->Document()->GetActiveViewTransition(); + if (NS_WARN_IF(!vt)) { + return kNoKey; + } + const auto* key = + Style()->GetPseudoType() == PseudoStyleType::viewTransitionOld + ? vt->GetOldImageKey(name, aManager, aResources) + : vt->GetNewImageKey(name); + return key ? *key : kNoKey; } AspectRatio nsImageFrame::ComputeIntrinsicRatioForImage( @@ -976,8 +997,8 @@ AspectRatio nsImageFrame::ComputeIntrinsicRatioForImage( } } - if (auto* surf = GetViewTransitionSurface()) { - return AspectRatio::FromSize(surf->GetSize()); + if (auto size = GetViewTransitionSnapshotSize()) { + return AspectRatio::FromSize(*size); } if (ShouldUseMappedAspectRatio()) { @@ -2344,35 +2365,17 @@ void nsDisplayImage::MaybeCreateWebRenderCommandsForViewTransition( nsDisplayListBuilder* aDisplayListBuilder) { auto* frame = Frame(); MOZ_ASSERT(!frame->mImage); - auto* surf = frame->GetViewTransitionSurface(); - if (NS_WARN_IF(!surf)) { + auto key = frame->GetViewTransitionImageKey(aManager, aResources); + if (NS_WARN_IF(key == kNoKey)) { return; } - if (!frame->mViewTransitionData.HasKey()) { - DataSourceSurface::ScopedMap map(surf, DataSourceSurface::READ); - if (NS_WARN_IF(!map.IsMapped())) { - return; - } - auto key = aManager->WrBridge()->GetNextImageKey(); - auto size = surf->GetSize(); - auto format = surf->GetFormat(); - wr::ImageDescriptor desc(size, format); - Range bytes(map.GetData(), map.GetStride() * size.height); - if (NS_WARN_IF(!aResources.AddImage(key, desc, bytes))) { - return; - } - // TODO: Discard this image - frame->mViewTransitionData.mImageKey = key; - frame->mViewTransitionData.mManager = aManager; - } const nsRect destAppUnits = GetDestRect(); const int32_t factor = mFrame->PresContext()->AppUnitsPerDevPixel(); const auto destRect = wr::ToLayoutRect(LayoutDeviceRect::FromAppUnits(destAppUnits, factor)); auto rendering = wr::ToImageRendering(frame->UsedImageRendering()); aBuilder.PushImage(destRect, destRect, !BackfaceIsHidden(), - /* aForceAntiAliasing = */ false, rendering, - frame->mViewTransitionData.mImageKey); + /* aForceAntiAliasing = */ false, rendering, key); } bool nsDisplayImage::CreateWebRenderCommands( @@ -2576,7 +2579,7 @@ void nsImageFrame::BuildDisplayList(nsDisplayListBuilder* aBuilder, aBuilder, this, clipFlags); if (!mComputedSize.IsEmpty()) { - const bool isViewTransition = mKind == Kind::ViewTransitionOld; + const bool isViewTransition = mKind == Kind::ViewTransition; const bool imageOK = mKind != Kind::ImageLoadingContent || ImageOk(mContent->AsElement()->State()); diff --git a/layout/generic/nsImageFrame.h b/layout/generic/nsImageFrame.h index 0a4c6c1aa89e..6f1e62e2459d 100644 --- a/layout/generic/nsImageFrame.h +++ b/layout/generic/nsImageFrame.h @@ -196,8 +196,9 @@ class nsImageFrame : public nsAtomicContainerFrame, public nsIReflowCallback { ContentPropertyAtIndex, // For a list-style-image ::marker. ListStyleImage, - // For a ::view-transition-old pseudo-element - ViewTransitionOld, + // For a ::view-transition-old or ::view-transition-new pseudo-element. + // Which one of the two is determined by the PseudoStyleType applying to us. + ViewTransition, }; // Creates a suitable continuing frame for this frame. @@ -216,8 +217,8 @@ class nsImageFrame : public nsAtomicContainerFrame, public nsIReflowCallback { ComputedStyle*); friend nsIFrame* NS_NewImageFrameForListStyleImage(mozilla::PresShell*, ComputedStyle*); - friend nsIFrame* NS_NewImageFrameForViewTransitionOld(mozilla::PresShell*, - ComputedStyle*); + friend nsIFrame* NS_NewImageFrameForViewTransition(mozilla::PresShell*, + ComputedStyle*); nsImageFrame(ComputedStyle* aStyle, nsPresContext* aPresContext, Kind aKind) : nsImageFrame(aStyle, aPresContext, kClassID, aKind) {} @@ -306,7 +307,11 @@ class nsImageFrame : public nsAtomicContainerFrame, public nsIReflowCallback { // and height="". bool ShouldUseMappedAspectRatio() const; - mozilla::gfx::DataSourceSurface* GetViewTransitionSurface() const; + nsAtom* GetViewTransitionName() const; + Maybe GetViewTransitionSnapshotSize() const; + mozilla::wr::ImageKey GetViewTransitionImageKey( + mozilla::layers::RenderRootStateManager*, + mozilla::wr::IpcResourceUpdateQueue&) const; /** * Notification that aRequest will now be the current request. @@ -398,15 +403,6 @@ class nsImageFrame : public nsAtomicContainerFrame, public nsIReflowCallback { nsCOMPtr mImage; nsCOMPtr mPrevImage; - struct ViewTransitionData { - // The image key of our snapshot. - mozilla::wr::ImageKey mImageKey{{0}, 0}; - // The owner of the key. - RefPtr mManager; - - bool HasKey() const { return mImageKey != mozilla::wr::ImageKey{{0}, 0}; } - } mViewTransitionData; - // The content-box size as if we are not fragmented, cached in the most recent // reflow. nsSize mComputedSize; diff --git a/layout/painting/nsDisplayList.cpp b/layout/painting/nsDisplayList.cpp index 3c4bef47ea69..b10764fe8e39 100644 --- a/layout/painting/nsDisplayList.cpp +++ b/layout/painting/nsDisplayList.cpp @@ -24,6 +24,7 @@ #include "mozilla/dom/HTMLCanvasElement.h" #include "mozilla/dom/RemoteBrowser.h" #include "mozilla/dom/Selection.h" +#include "mozilla/dom/ViewTransition.h" #include "mozilla/dom/ServiceWorkerRegistrar.h" #include "mozilla/dom/ServiceWorkerRegistration.h" #include "mozilla/dom/SVGElement.h" @@ -5203,10 +5204,11 @@ bool nsDisplayOwnLayer::CreateWebRenderCommands( const StackingContextHelper& aSc, RenderRootStateManager* aManager, nsDisplayListBuilder* aDisplayListBuilder) { Maybe prop; - bool needsProp = aManager->LayerManager()->AsyncPanZoomEnabled() && - (IsScrollThumbLayer() || IsZoomingLayer() || - ShouldGetFixedAnimationId() || - (IsRootScrollbarContainer() && HasDynamicToolbar())); + Maybe snapshot; + const bool needsProp = aManager->LayerManager()->AsyncPanZoomEnabled() && + (IsScrollThumbLayer() || IsZoomingLayer() || + ShouldGetFixedAnimationId() || + (IsRootScrollbarContainer() && HasDynamicToolbar())); if (needsProp) { // APZ is enabled and this is a scroll thumb or zooming layer, so we need @@ -5228,12 +5230,33 @@ bool nsDisplayOwnLayer::CreateWebRenderCommands( params.animation = prop.ptrOr(nullptr); params.clip = wr::WrStackingContextClip::ClipChain(aBuilder.CurrentClipChainId()); - if (IsScrollbarContainer() && IsRootScrollbarContainer()) { + const bool rootScrollbarContainer = IsRootScrollbarContainer(); + if (rootScrollbarContainer) { params.prim_flags |= wr::PrimitiveFlags::IS_SCROLLBAR_CONTAINER; } - if (IsZoomingLayer() || - (ShouldGetFixedAnimationId() || - (IsRootScrollbarContainer() && HasDynamicToolbar()))) { + if (mFrame->HasAnyStateBits(NS_FRAME_CAPTURED_IN_VIEW_TRANSITION)) { + auto key = [&]() -> Maybe { + auto* vt = mFrame->PresContext()->Document()->GetActiveViewTransition(); + if (NS_WARN_IF(!vt)) { + return Nothing(); + } + const auto* key = + vt->GetImageKeyForCapturedFrame(mFrame, aManager, aResources); + return key ? Some(wr::SnapshotImageKey{*key}) : Nothing(); + }(); + if (key) { + float auPerDevPixel = mFrame->PresContext()->AppUnitsPerDevPixel(); + snapshot.emplace(wr::SnapshotInfo{ + .key = *key, + .area = wr::ToLayoutRect(LayoutDeviceRect::FromAppUnits( + mFrame->InkOverflowRectRelativeToSelf(), auPerDevPixel)), + .detached = true, + }); + params.snapshot = snapshot.ptr(); + } + } + if (IsZoomingLayer() || ShouldGetFixedAnimationId() || + (rootScrollbarContainer && HasDynamicToolbar())) { params.is_2d_scale_translation = true; params.should_snap = true; } diff --git a/layout/painting/nsDisplayList.h b/layout/painting/nsDisplayList.h index 240114d59519..07b189a97e84 100644 --- a/layout/painting/nsDisplayList.h +++ b/layout/painting/nsDisplayList.h @@ -5380,7 +5380,7 @@ class nsDisplayOwnLayer : public nsDisplayWrapList { public: enum OwnLayerType { OwnLayerForTransformWithRoundedClip, - OwnLayerForStackingContext, + OwnLayerForViewTransitionCapture, OwnLayerForScrollbar, OwnLayerForScrollThumb, OwnLayerForSubdoc, diff --git a/modules/libpref/init/StaticPrefList.yaml b/modules/libpref/init/StaticPrefList.yaml index d8414862321a..7c1dc1ce9c09 100644 --- a/modules/libpref/init/StaticPrefList.yaml +++ b/modules/libpref/init/StaticPrefList.yaml @@ -4923,6 +4923,12 @@ value: false mirror: always +# Whether view transitions use webrender for live-capture +- name: dom.viewTransitions.live-capture + type: bool + value: false + mirror: always + # Is support for WebVR APIs enabled? # Disabled everywhere, but not removed. - name: dom.vr.enabled