Bug 1922333 - Land view transition plumbing for live capture behind a default-off pref. r=view-transitions-reviewers,boris

It is mostly working, but there are some tests that hit crashes and
asserts that we should sort out. For now, land it default-off.

This does implement other bits that are on by default tho, like creating
image frames for ::view-transition-new() pseudo-elements and so on. I
think that is fine (it's just that for now they are all transparent).

Differential Revision: https://phabricator.services.mozilla.com/D239382
This commit is contained in:
Emilio Cobos Álvarez
2025-03-05 11:18:18 +00:00
parent d58176b429
commit 0ff8015e82
18 changed files with 388 additions and 103 deletions

View File

@@ -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<gfx::DataSourceSurface> 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<gfx::DataSourceSurface> mFallback;
RefPtr<layers::RenderRootStateManager> 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<uint8_t> 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<gfx::DataSourceSurface> 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<Element> mNewElement;
wr::SnapshotImageKey mNewSnapshotKey{kNoKey};
nsSize mNewSnapshotSize;
CapturedElement() = default;
@@ -135,6 +215,14 @@ struct ViewTransition::CapturedElement {
RefPtr<StyleLockedDeclarationBlock> mOldRule;
// The rules for ::view-transition-new(<name>).
RefPtr<StyleLockedDeclarationBlock> 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<nsSize> ViewTransition::GetOldSize(nsAtom* aName) const {
auto* el = mNamedElements.Get(aName);
if (NS_WARN_IF(!el)) {
return {};
}
return Some(el->mOldState.mSnapshot.mSize);
}
Maybe<nsSize> 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) |
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<SkipTransitionReason> ViewTransition::CaptureNewState() {
auto& capturedElement = mNamedElements.LookupOrInsertWith(
name, [&] { return MakeUnique<CapturedElement>(); });
capturedElement->mNewElement = aFrame->GetContent()->AsElement();
capturedElement->mNewSnapshotSize =
aFrame->InkOverflowRectRelativeToSelf().Size();
aFrame->AddStateBits(NS_FRAME_CAPTURED_IN_VIEW_TRANSITION);
return true;
});

View File

@@ -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<nsSize> GetOldSize(nsAtom* aName) const;
Maybe<nsSize> 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;

View File

@@ -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;

View File

@@ -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<uint8_t> 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));
}

View File

@@ -132,6 +132,8 @@ class IpcResourceUpdateQueue {
bool AddBlobImage(wr::BlobImageKey aKey, const ImageDescriptor& aDescriptor,
Range<uint8_t> 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<uint8_t> aBytes, uint32_t aIndex);
bool AddFontDescriptor(wr::FontKey aKey, Range<uint8_t> aBytes,

View File

@@ -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() {

View File

@@ -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<wr::IpcResourceUpdateQueue> mAsyncResourceUpdates;
nsTArray<wr::ImageKey> mImageKeysToDelete;
nsTArray<wr::BlobImageKey> mBlobImageKeysToDelete;
nsTArray<wr::SnapshotImageKey> mSnapshotImageKeysToDelete;
std::unordered_map<uint64_t, RefPtr<SharedSurfacesAnimation>>
mAsyncAnimations;

View File

@@ -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

View File

@@ -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;
}

View File

@@ -144,6 +144,12 @@ inline auto TiedFields<mozilla::wr::BlobImageKey>(
return std::tie(a._0);
}
template <>
inline auto TiedFields<mozilla::wr::SnapshotImageKey>(
mozilla::wr::SnapshotImageKey& a) {
return std::tie(a._0);
}
template <>
inline auto TiedFields<mozilla::wr::FontKey>(mozilla::wr::FontKey& a) {
return std::tie(a.mNamespace, a.mHandle);
@@ -340,6 +346,10 @@ template <>
struct ParamTraits<mozilla::wr::BlobImageKey>
: public ParamTraits_TiedFields<mozilla::wr::BlobImageKey> {};
template <>
struct ParamTraits<mozilla::wr::SnapshotImageKey>
: public ParamTraits_TiedFields<mozilla::wr::SnapshotImageKey> {};
template <>
struct ParamTraits<mozilla::wr::FontKey>
: public ParamTraits_TiedFields<mozilla::wr::FontKey> {};

View File

@@ -113,9 +113,10 @@ template struct mozilla::wr::Box2D<int, mozilla::wr::LayoutPixel>;
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

View File

@@ -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;
}
}

View File

@@ -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<nsDisplayOwnLayer>(
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

View File

@@ -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,
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<nsSize> 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<uint8_t> 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());

View File

@@ -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,7 +217,7 @@ class nsImageFrame : public nsAtomicContainerFrame, public nsIReflowCallback {
ComputedStyle*);
friend nsIFrame* NS_NewImageFrameForListStyleImage(mozilla::PresShell*,
ComputedStyle*);
friend nsIFrame* NS_NewImageFrameForViewTransitionOld(mozilla::PresShell*,
friend nsIFrame* NS_NewImageFrameForViewTransition(mozilla::PresShell*,
ComputedStyle*);
nsImageFrame(ComputedStyle* aStyle, nsPresContext* aPresContext, Kind 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<nsSize> 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<imgIContainer> mImage;
nsCOMPtr<imgIContainer> mPrevImage;
struct ViewTransitionData {
// The image key of our snapshot.
mozilla::wr::ImageKey mImageKey{{0}, 0};
// The owner of the key.
RefPtr<mozilla::layers::RenderRootStateManager> 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;

View File

@@ -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,7 +5204,8 @@ bool nsDisplayOwnLayer::CreateWebRenderCommands(
const StackingContextHelper& aSc, RenderRootStateManager* aManager,
nsDisplayListBuilder* aDisplayListBuilder) {
Maybe<wr::WrAnimationProperty> prop;
bool needsProp = aManager->LayerManager()->AsyncPanZoomEnabled() &&
Maybe<wr::SnapshotInfo> snapshot;
const bool needsProp = aManager->LayerManager()->AsyncPanZoomEnabled() &&
(IsScrollThumbLayer() || IsZoomingLayer() ||
ShouldGetFixedAnimationId() ||
(IsRootScrollbarContainer() && HasDynamicToolbar()));
@@ -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<wr::SnapshotImageKey> {
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;
}

View File

@@ -5380,7 +5380,7 @@ class nsDisplayOwnLayer : public nsDisplayWrapList {
public:
enum OwnLayerType {
OwnLayerForTransformWithRoundedClip,
OwnLayerForStackingContext,
OwnLayerForViewTransitionCapture,
OwnLayerForScrollbar,
OwnLayerForScrollThumb,
OwnLayerForSubdoc,

View File

@@ -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