diff options
Diffstat (limited to '')
60 files changed, 914 insertions, 879 deletions
diff --git a/gfx/layers/CanvasDrawEventRecorder.cpp b/gfx/layers/CanvasDrawEventRecorder.cpp index af143bf5cb..6140fc4ba7 100644 --- a/gfx/layers/CanvasDrawEventRecorder.cpp +++ b/gfx/layers/CanvasDrawEventRecorder.cpp @@ -127,6 +127,7 @@ int64_t CanvasDrawEventRecorder::CreateCheckpoint() { int64_t checkpoint = mHeader->eventCount; RecordEvent(RecordedCheckpoint()); ClearProcessedExternalSurfaces(); + ClearProcessedExternalImages(); return checkpoint; } @@ -276,6 +277,7 @@ void CanvasDrawEventRecorder::DropFreeBuffers() { } ClearProcessedExternalSurfaces(); + ClearProcessedExternalImages(); } void CanvasDrawEventRecorder::IncrementEventCount() { @@ -444,6 +446,16 @@ void CanvasDrawEventRecorder::StoreSourceSurfaceRecording( DrawEventRecorderPrivate::StoreSourceSurfaceRecording(aSurface, aReason); } +void CanvasDrawEventRecorder::StoreImageRecording( + const RefPtr<Image>& aImageOfSurfaceDescriptor, const char* aReasony) { + NS_ASSERT_OWNINGTHREAD(CanvasDrawEventRecorder); + + StoreExternalImageRecording(aImageOfSurfaceDescriptor); + mExternalImages.back().mEventCount = mHeader->eventCount; + + ClearProcessedExternalImages(); +} + void CanvasDrawEventRecorder::ClearProcessedExternalSurfaces() { while (!mExternalSurfaces.empty()) { if (mExternalSurfaces.front().mEventCount > mHeader->processedCount) { @@ -453,5 +465,14 @@ void CanvasDrawEventRecorder::ClearProcessedExternalSurfaces() { } } +void CanvasDrawEventRecorder::ClearProcessedExternalImages() { + while (!mExternalImages.empty()) { + if (mExternalImages.front().mEventCount > mHeader->processedCount) { + break; + } + mExternalImages.pop_front(); + } +} + } // namespace layers } // namespace mozilla diff --git a/gfx/layers/CanvasDrawEventRecorder.h b/gfx/layers/CanvasDrawEventRecorder.h index c9eacf27ac..aa95eec3e6 100644 --- a/gfx/layers/CanvasDrawEventRecorder.h +++ b/gfx/layers/CanvasDrawEventRecorder.h @@ -111,6 +111,9 @@ class CanvasDrawEventRecorder final : public gfx::DrawEventRecorderPrivate, void StoreSourceSurfaceRecording(gfx::SourceSurface* aSurface, const char* aReason) final; + void StoreImageRecording(const RefPtr<Image>& aImageOfSurfaceDescriptor, + const char* aReasony) final; + gfx::RecorderType GetRecorderType() const override { return gfx::RecorderType::CANVAS; } @@ -134,6 +137,8 @@ class CanvasDrawEventRecorder final : public gfx::DrawEventRecorderPrivate, void ClearProcessedExternalSurfaces(); + void ClearProcessedExternalImages(); + protected: gfx::ContiguousBuffer& GetContiguousBuffer(size_t aSize) final; diff --git a/gfx/layers/FrameMetrics.h b/gfx/layers/FrameMetrics.h index 5d13d36703..c79a088e53 100644 --- a/gfx/layers/FrameMetrics.h +++ b/gfx/layers/FrameMetrics.h @@ -700,7 +700,7 @@ MOZ_DEFINE_ENUM_CLASS_WITH_BASE( std::ostream& operator<<(std::ostream& aStream, const OverscrollBehavior& aBehavior); -struct OverscrollBehaviorInfo { +struct OverscrollBehaviorInfo final { OverscrollBehaviorInfo(); // Construct from StyleOverscrollBehavior values. @@ -711,6 +711,8 @@ struct OverscrollBehaviorInfo { friend std::ostream& operator<<(std::ostream& aStream, const OverscrollBehaviorInfo& aInfo); + auto MutTiedFields() { return std::tie(mBehaviorX, mBehaviorY); } + OverscrollBehavior mBehaviorX; OverscrollBehavior mBehaviorY; }; diff --git a/gfx/layers/LayersTypes.h b/gfx/layers/LayersTypes.h index 340ea76fa5..c02851d81f 100644 --- a/gfx/layers/LayersTypes.h +++ b/gfx/layers/LayersTypes.h @@ -46,9 +46,11 @@ class TextureHost; #undef NONE #undef OPAQUE -struct LayersId { +struct LayersId final { uint64_t mId = 0; + auto MutTiedFields() { return std::tie(mId); } + bool IsValid() const { return mId != 0; } // Allow explicit cast to a uint64_t for now @@ -75,9 +77,11 @@ struct LayersId { }; template <typename T> -struct BaseTransactionId { +struct BaseTransactionId final { uint64_t mId = 0; + auto MutTiedFields() { return std::tie(mId); } + bool IsValid() const { return mId != 0; } [[nodiscard]] BaseTransactionId<T> Next() const { diff --git a/gfx/layers/NativeLayerCA.h b/gfx/layers/NativeLayerCA.h index b41ac36c23..93b6b3a6de 100644 --- a/gfx/layers/NativeLayerCA.h +++ b/gfx/layers/NativeLayerCA.h @@ -143,7 +143,7 @@ class NativeLayerRootCA : public NativeLayerRoot { void SetWindowIsFullscreen(bool aFullscreen); - VideoLowPowerType CheckVideoLowPower(); + VideoLowPowerType CheckVideoLowPower(const MutexAutoLock& aProofOfLock); protected: explicit NativeLayerRootCA(CALayer* aLayer); @@ -335,8 +335,7 @@ class NativeLayerCA : public NativeLayer { Maybe<SurfaceWithInvalidRegion> GetUnusedSurfaceAndCleanUp( const MutexAutoLock& aProofOfLock); - bool IsVideo(); - bool IsVideoAndLocked(const MutexAutoLock& aProofOfLock); + bool IsVideo(const MutexAutoLock& aProofOfLock); bool ShouldSpecializeVideo(const MutexAutoLock& aProofOfLock); bool HasExtent() const { return mHasExtent; } void SetHasExtent(bool aHasExtent) { mHasExtent = aHasExtent; } @@ -484,6 +483,7 @@ class NativeLayerCA : public NativeLayer { bool mSpecializeVideo = false; bool mHasExtent = false; bool mIsDRM = false; + bool mIsTextureHostVideo = false; #ifdef NIGHTLY_BUILD // Track the consistency of our caller's API usage. Layers that are drawn diff --git a/gfx/layers/NativeLayerCA.mm b/gfx/layers/NativeLayerCA.mm index 42a889184e..a983e3a45e 100644 --- a/gfx/layers/NativeLayerCA.mm +++ b/gfx/layers/NativeLayerCA.mm @@ -330,37 +330,38 @@ bool NativeLayerRootCA::CommitToScreen() { mWindowIsFullscreen); mCommitPending = false; - } - if (StaticPrefs::gfx_webrender_debug_dump_native_layer_tree_to_file()) { - static uint32_t sFrameID = 0; - uint32_t frameID = sFrameID++; - - NSString* dirPath = - [NSString stringWithFormat:@"%@/Desktop/nativelayerdumps-%d", - NSHomeDirectory(), getpid()]; - if ([NSFileManager.defaultManager createDirectoryAtPath:dirPath - withIntermediateDirectories:YES - attributes:nil - error:nullptr]) { - NSString* filename = - [NSString stringWithFormat:@"frame-%d.html", frameID]; - NSString* filePath = [dirPath stringByAppendingPathComponent:filename]; - DumpLayerTreeToFile([filePath UTF8String]); - } else { - NSLog(@"Failed to create directory %@", dirPath); + if (StaticPrefs::gfx_webrender_debug_dump_native_layer_tree_to_file()) { + static uint32_t sFrameID = 0; + uint32_t frameID = sFrameID++; + + NSString* dirPath = + [NSString stringWithFormat:@"%@/Desktop/nativelayerdumps-%d", + NSHomeDirectory(), getpid()]; + if ([NSFileManager.defaultManager createDirectoryAtPath:dirPath + withIntermediateDirectories:YES + attributes:nil + error:nullptr]) { + NSString* filename = + [NSString stringWithFormat:@"frame-%d.html", frameID]; + NSString* filePath = [dirPath stringByAppendingPathComponent:filename]; + DumpLayerTreeToFile([filePath UTF8String]); + } else { + NSLog(@"Failed to create directory %@", dirPath); + } } - } - // Decide if we are going to emit telemetry about video low power on this - // commit. - static const int32_t TELEMETRY_COMMIT_PERIOD = - StaticPrefs::gfx_core_animation_low_power_telemetry_frames_AtStartup(); - mTelemetryCommitCount = (mTelemetryCommitCount + 1) % TELEMETRY_COMMIT_PERIOD; - if (mTelemetryCommitCount == 0) { - // Figure out if we are hitting video low power mode. - VideoLowPowerType videoLowPower = CheckVideoLowPower(); - EmitTelemetryForVideoLowPower(videoLowPower); + // Decide if we are going to emit telemetry about video low power on this + // commit. + static const int32_t TELEMETRY_COMMIT_PERIOD = + StaticPrefs::gfx_core_animation_low_power_telemetry_frames_AtStartup(); + mTelemetryCommitCount = + (mTelemetryCommitCount + 1) % TELEMETRY_COMMIT_PERIOD; + if (mTelemetryCommitCount == 0) { + // Figure out if we are hitting video low power mode. + VideoLowPowerType videoLowPower = CheckVideoLowPower(lock); + EmitTelemetryForVideoLowPower(videoLowPower); + } } return true; @@ -579,7 +580,8 @@ void NativeLayerRootCA::SetWindowIsFullscreen(bool aFullscreen) { return components[componentCount - 1] >= 1.0f; } -VideoLowPowerType NativeLayerRootCA::CheckVideoLowPower() { +VideoLowPowerType NativeLayerRootCA::CheckVideoLowPower( + const MutexAutoLock& aProofOfLock) { // This deteremines whether the current layer contents qualify for the // macOS Core Animation video low power mode. Those requirements are // summarized at @@ -609,7 +611,7 @@ VideoLowPowerType NativeLayerRootCA::CheckVideoLowPower() { secondCALayer = topCALayer; topCALayer = topLayer->UnderlyingCALayer(WhichRepresentation::ONSCREEN); - topLayerIsVideo = topLayer->IsVideo(); + topLayerIsVideo = topLayer->IsVideo(aProofOfLock); if (topLayerIsVideo) { ++videoLayerCount; } @@ -835,9 +837,8 @@ NativeLayerCA::NativeLayerCA(bool aIsOpaque) mIsOpaque(aIsOpaque) { #ifdef NIGHTLY_BUILD if (StaticPrefs::gfx_core_animation_specialize_video_log()) { - NSLog(@"VIDEO_LOG: NativeLayerCA: %p is being created to host video, which " - @"will force a video " - @"layer rebuild.", + NSLog(@"VIDEO_LOG: NativeLayerCA: %p is being created to host an external " + @"image, which may force a video layer rebuild.", this); } #endif @@ -864,7 +865,7 @@ NativeLayerCA::~NativeLayerCA() { if (mHasEverAttachExternalImage && StaticPrefs::gfx_core_animation_specialize_video_log()) { NSLog(@"VIDEO_LOG: ~NativeLayerCA: %p is being destroyed after hosting " - @"video.", + @"an external image.", this); } #endif @@ -902,6 +903,9 @@ void NativeLayerCA::AttachExternalImage(wr::RenderTextureHost* aExternalImage) { return; } + // Determine if TextureHost is a video surface. + mIsTextureHostVideo = gfx::Info(mTextureHost->GetFormat())->isYuv; + gfx::IntSize oldSize = mSize; mSize = texture->GetSize(0); bool changedSizeAndDisplayRect = (mSize != oldSize); @@ -933,18 +937,15 @@ void NativeLayerCA::AttachExternalImage(wr::RenderTextureHost* aExternalImage) { }); } -bool NativeLayerCA::IsVideo() { - // Anything with a texture host is considered a video source. - return mTextureHost; -} - -bool NativeLayerCA::IsVideoAndLocked(const MutexAutoLock& aProofOfLock) { - // Anything with a texture host is considered a video source. - return mTextureHost; +bool NativeLayerCA::IsVideo(const MutexAutoLock& aProofOfLock) { + // If we have a texture host, we've checked to see if it's providing video. + // And if we don't have a texture host, it isn't video, so we just check + // the value we've computed. + return mIsTextureHostVideo; } bool NativeLayerCA::ShouldSpecializeVideo(const MutexAutoLock& aProofOfLock) { - if (!IsVideoAndLocked(aProofOfLock)) { + if (!IsVideo(aProofOfLock)) { // Only videos are eligible. return false; } @@ -1410,6 +1411,8 @@ void NativeLayerCA::NotifySurfaceReady() { mInProgressSurface, "NotifySurfaceReady called without preceding call to NextSurface"); + mIsTextureHostVideo = false; + if (mInProgressLockedIOSurface) { mInProgressLockedIOSurface->Unlock(false); mInProgressLockedIOSurface = nullptr; @@ -1465,7 +1468,7 @@ void NativeLayerCA::ForAllRepresentations(F aFn) { NativeLayerCA::UpdateType NativeLayerCA::HasUpdate( WhichRepresentation aRepresentation) { MutexAutoLock lock(mMutex); - return GetRepresentation(aRepresentation).HasUpdate(IsVideoAndLocked(lock)); + return GetRepresentation(aRepresentation).HasUpdate(IsVideo(lock)); } /* static */ @@ -1510,7 +1513,7 @@ bool NativeLayerCA::ApplyChanges(WhichRepresentation aRepresentation, .ApplyChanges(aUpdate, mSize, mIsOpaque, mPosition, mTransform, mDisplayRect, mClipRect, mBackingScale, mSurfaceIsFlipped, mSamplingFilter, mSpecializeVideo, surface, mColor, mIsDRM, - IsVideo()); + IsVideo(lock)); } CALayer* NativeLayerCA::UnderlyingCALayer(WhichRepresentation aRepresentation) { diff --git a/gfx/layers/RemoteTextureMap.cpp b/gfx/layers/RemoteTextureMap.cpp index 3fe3b13deb..95db676651 100644 --- a/gfx/layers/RemoteTextureMap.cpp +++ b/gfx/layers/RemoteTextureMap.cpp @@ -19,6 +19,7 @@ #include "mozilla/layers/RemoteTextureHostWrapper.h" #include "mozilla/layers/TextureClientSharedSurface.h" #include "mozilla/layers/WebRenderTextureHost.h" +#include "mozilla/StaticPrefs_gfx.h" #include "mozilla/StaticPrefs_webgl.h" #include "mozilla/webgpu/ExternalTexture.h" #include "mozilla/webrender/RenderThread.h" @@ -334,7 +335,9 @@ bool RemoteTextureMap::RecycleTexture( // Recycle texture data recycled.mTextureData = std::move(aHolder.mTextureData); } - aRecycleBin->mRecycledTextures.push_back(std::move(recycled)); + if (!StaticPrefs::gfx_remote_texture_recycle_disabled()) { + aRecycleBin->mRecycledTextures.push_back(std::move(recycled)); + } return true; } diff --git a/gfx/layers/apz/public/APZPublicUtils.h b/gfx/layers/apz/public/APZPublicUtils.h index 6433008b4c..47a07c8fd6 100644 --- a/gfx/layers/apz/public/APZPublicUtils.h +++ b/gfx/layers/apz/public/APZPublicUtils.h @@ -55,9 +55,8 @@ const ScreenMargin CalculatePendingDisplayPort( * between 1 and 8 inclusive. The multiplier is chosen based on the provided * base size, such that multiplier is larger when the base size is larger. * The exact details are somewhat arbitrary and tuned by hand. - * This function is intended to only be used with WebRender, because that is - * the codepath that wants to use a larger displayport alignment, because - * moving the displayport is relatively expensive with WebRender. + * We use a large displayport alignment because moving the displayport is + * relatively expensive with WebRender. */ gfx::IntSize GetDisplayportAlignmentMultiplier(const ScreenSize& aBaseSize); diff --git a/gfx/layers/apz/public/GeckoContentControllerTypes.h b/gfx/layers/apz/public/GeckoContentControllerTypes.h index 8ab478eab5..8616455137 100644 --- a/gfx/layers/apz/public/GeckoContentControllerTypes.h +++ b/gfx/layers/apz/public/GeckoContentControllerTypes.h @@ -24,7 +24,7 @@ MOZ_DEFINE_ENUM_CLASS(GeckoContentController_APZStateChange, ( eTransformEnd, /** * APZ started a touch. - * |aArg| is 1 if touch can be a pan, 0 otherwise. + * |aArg| is 1 if touch can be a pan or zoom, 0 otherwise. */ eStartTouch, /** @@ -33,7 +33,7 @@ MOZ_DEFINE_ENUM_CLASS(GeckoContentController_APZStateChange, ( eStartPanning, /** * APZ finished processing a touch. - * |aArg| is 1 if touch was a click, 0 otherwise. + * |aArg| is a `apz::SingleTapState` defined in APZUtils.h. */ eEndTouch )); diff --git a/gfx/layers/apz/src/APZCTreeManager.cpp b/gfx/layers/apz/src/APZCTreeManager.cpp index ef3cde3596..bc0b6a8dc6 100644 --- a/gfx/layers/apz/src/APZCTreeManager.cpp +++ b/gfx/layers/apz/src/APZCTreeManager.cpp @@ -145,8 +145,7 @@ struct APZCTreeManager::TreeBuildingState { // root node of the layers (sub-)tree, which may not be same as the RCD node // for the subtree, and so we need this mechanism to ensure it gets propagated // to the RCD's APZC instance. Once it is set on the APZC instance, the value - // is cleared back to Nothing(). Note that this is only used in the WebRender - // codepath. + // is cleared back to Nothing(). Maybe<uint64_t> mZoomAnimationId; // See corresponding members of APZCTreeManager. These are the same thing, but @@ -538,12 +537,9 @@ APZCTreeManager::UpdateHitTestingTree(const WebRenderScrollDataWrapper& aRoot, AsyncPanZoomController* apzc = node->GetApzc(); aLayerMetrics.SetApzc(apzc); - // GetScrollbarAnimationId is only set when webrender is enabled, - // which limits the extra thumb mapping work to the webrender-enabled - // case where it is needed. - // Note also that when webrender is enabled, a "valid" animation id - // is always nonzero, so we don't need to worry about handling the - // case where WR is enabled and the animation id is zero. + // Note that a "valid" animation id is always nonzero, so we don't + // need to worry about handling the case where the animation id is + // zero. if (node->GetScrollbarAnimationId()) { if (node->IsScrollThumbNode()) { state.mScrollThumbs.push_back(node); @@ -555,11 +551,9 @@ APZCTreeManager::UpdateHitTestingTree(const WebRenderScrollDataWrapper& aRoot, } } - // GetFixedPositionAnimationId is only set when webrender is enabled. if (node->GetFixedPositionAnimationId().isSome()) { state.mFixedPositionInfo.emplace_back(node); } - // GetStickyPositionAnimationId is only set when webrender is enabled. if (node->GetStickyPositionAnimationId().isSome()) { state.mStickyPositionInfo.emplace_back(node); } @@ -2290,8 +2284,7 @@ void APZCTreeManager::SetupScrollbarDrag( // Under some conditions, we can confirm the drag block right away. // Otherwise, we have to wait for a main-thread confirmation. - if (StaticPrefs::apz_drag_initial_enabled() && - // check that the scrollbar's target scroll frame is layerized + if (/* check that the scrollbar's target scroll frame is layerized */ aScrollThumbNode->GetScrollTargetId() == aApzc->GetGuid().mScrollId && !aApzc->IsScrollInfoLayer()) { uint64_t dragBlockId = dragBlock->GetBlockId(); @@ -3538,12 +3531,6 @@ already_AddRefed<AsyncPanZoomController> APZCTreeManager::CommonAncestor( } bool APZCTreeManager::IsFixedToRootContent( - const HitTestingTreeNode* aNode) const { - MutexAutoLock lock(mMapLock); - return IsFixedToRootContent(FixedPositionInfo(aNode), lock); -} - -bool APZCTreeManager::IsFixedToRootContent( const FixedPositionInfo& aFixedInfo, const MutexAutoLock& aProofOfMapLock) const { ScrollableLayerGuid::ViewID fixedTarget = aFixedInfo.mFixedPosTarget; diff --git a/gfx/layers/apz/src/APZCTreeManager.h b/gfx/layers/apz/src/APZCTreeManager.h index 71d35fd5a6..939e7572a4 100644 --- a/gfx/layers/apz/src/APZCTreeManager.h +++ b/gfx/layers/apz/src/APZCTreeManager.h @@ -199,12 +199,10 @@ class APZCTreeManager : public IAPZCTreeManager, public APZInputBridge { LayersId aOriginatingLayersId, uint32_t aPaintSequenceNumber); /** - * Called when webrender is enabled, from the sampler thread. This function - * populates the provided transaction with any async scroll offsets needed. - * It also advances APZ animations to the specified sample time, and requests - * another composite if there are still active animations. - * In effect it is the webrender equivalent of (part of) the code in - * AsyncCompositionManager. + * Called from the sampler thread. This function populates the provided + * transaction with any async scroll offsets needed. It also advances APZ + * animations to the specified sample time, and requests another composite if + * there are still active animations. */ void SampleForWebRender(const Maybe<VsyncId>& aVsyncId, wr::TransactionWrapper& aTxn, @@ -509,7 +507,7 @@ class APZCTreeManager : public IAPZCTreeManager, public APZInputBridge { void AssertOnUpdaterThread(); // Returns a pointer to the WebRenderAPI this APZCTreeManager is for. - // This might be null (for example, if WebRender is not enabled). + // This might be null (for example, during GTests). already_AddRefed<wr::WebRenderAPI> GetWebRenderAPI() const; protected: @@ -679,12 +677,8 @@ class APZCTreeManager : public IAPZCTreeManager, public APZInputBridge { struct FixedPositionInfo; struct StickyPositionInfo; - // Returns true if |aNode| is a fixed layer that is fixed to the root content - // APZC. - // The map lock is required within these functions; if the map lock is already - // being held by the caller, the second overload should be used. If the map - // lock is not being held at the call site, the first overload should be used. - bool IsFixedToRootContent(const HitTestingTreeNode* aNode) const; + // Returns true if |aFixedInfo| represents a layer that is fixed to the root + // content APZC. bool IsFixedToRootContent(const FixedPositionInfo& aFixedInfo, const MutexAutoLock& aProofOfMapLock) const; @@ -919,15 +913,14 @@ class APZCTreeManager : public IAPZCTreeManager, public APZInputBridge { } }; /** - * If this APZCTreeManager is being used with WebRender, this vector gets - * populated during a layers update. It holds a package of information needed - * to compute and set the async transforms on scroll thumbs. This information - * is extracted from the HitTestingTreeNodes for the WebRender case because - * accessing the HitTestingTreeNodes requires holding the tree lock which - * we cannot do on the WR sampler thread. mScrollThumbInfo, however, can + * This vector gets populated during a layers update. It holds a package of + * information needed to compute and set the async transforms on scroll + * thumbs. This information is extracted from the HitTestingTreeNodes because + * accessing the HitTestingTreeNodes requires holding the tree lock which we + * cannot do on the WebRender sampler thread. mScrollThumbInfo, however, can * be accessed while just holding the mMapLock which is safe to do on the - * sampler thread. - * mMapLock must be acquired while accessing or modifying mScrollThumbInfo. + * sampler thread. mMapLock must be acquired while accessing or modifying + * mScrollThumbInfo. */ std::vector<ScrollThumbInfo> mScrollThumbInfo; @@ -945,12 +938,11 @@ class APZCTreeManager : public IAPZCTreeManager, public APZInputBridge { mScrollDirection(aScrollDirection) {} }; /** - * If this APZCTreeManager is being used with WebRender, this vector gets - * populated during a layers update. It holds a package of information needed - * to compute and set the async transforms on root scrollbars. This - * information is extracted from the HitTestingTreeNodes for the WebRender - * case because accessing the HitTestingTreeNodes requires holding the tree - * lock which we cannot do on the WR sampler thread. mRootScrollbarInfo, + * This vector gets populated during a layers update. It holds a package of + * information needed to compute and set the async transforms on root + * scrollbars. This information is extracted from the HitTestingTreeNodes + * because accessing the HitTestingTreeNodes requires holding the tree lock + * which we cannot do on the WebRender sampler thread. mRootScrollbarInfo, * however, can be accessed while just holding the mMapLock which is safe to * do on the sampler thread. * mMapLock must be acquired while accessing or modifying mRootScrollbarInfo. @@ -970,15 +962,14 @@ class APZCTreeManager : public IAPZCTreeManager, public APZInputBridge { explicit FixedPositionInfo(const HitTestingTreeNode* aNode); }; /** - * If this APZCTreeManager is being used with WebRender, this vector gets - * populated during a layers update. It holds a package of information needed - * to compute and set the async transforms on fixed position content. This - * information is extracted from the HitTestingTreeNodes for the WebRender - * case because accessing the HitTestingTreeNodes requires holding the tree - * lock which we cannot do on the WR sampler thread. mFixedPositionInfo, - * however, can be accessed while just holding the mMapLock which is safe to - * do on the sampler thread. mMapLock must be acquired while accessing or - * modifying mFixedPositionInfo. + * This vector gets populated during a layers update. It holds a package of + * information needed to compute and set the async transforms on fixed + * position content. This information is extracted from the + * HitTestingTreeNodes because accessing the HitTestingTreeNodes requires + * holding the tree lock which we cannot do on the WebRender sampler thread. + * mFixedPositionInfo, however, can be accessed while just holding the + * mMapLock which is safe to do on the sampler thread. mMapLock must be + * acquired while accessing or modifying mFixedPositionInfo. */ std::vector<FixedPositionInfo> mFixedPositionInfo; @@ -997,15 +988,14 @@ class APZCTreeManager : public IAPZCTreeManager, public APZInputBridge { explicit StickyPositionInfo(const HitTestingTreeNode* aNode); }; /** - * If this APZCTreeManager is being used with WebRender, this vector gets - * populated during a layers update. It holds a package of information needed - * to compute and set the async transforms on sticky position content. This - * information is extracted from the HitTestingTreeNodes for the WebRender - * case because accessing the HitTestingTreeNodes requires holding the tree - * lock which we cannot do on the WR sampler thread. mStickyPositionInfo, - * however, can be accessed while just holding the mMapLock which is safe to - * do on the sampler thread. mMapLock must be acquired while accessing or - * modifying mStickyPositionInfo. + * This vector gets populated during a layers update. It holds a package of + * information needed to compute and set the async transforms on sticky + * position content. This information is extracted from the + * HitTestingTreeNodes because accessing the HitTestingTreeNodes requires + * holding the tree lock which we cannot do on the WebRender sampler thread. + * mStickyPositionInfo, however, can be accessed while just holding the + * mMapLock which is safe to do on the sampler thread. mMapLock must be + * acquired while accessing or modifying mStickyPositionInfo. */ std::vector<StickyPositionInfo> mStickyPositionInfo; diff --git a/gfx/layers/apz/src/APZSampler.cpp b/gfx/layers/apz/src/APZSampler.cpp index d0e251cec4..e14535da0d 100644 --- a/gfx/layers/apz/src/APZSampler.cpp +++ b/gfx/layers/apz/src/APZSampler.cpp @@ -125,19 +125,16 @@ AsyncTransform APZSampler::GetCurrentAsyncTransform( ParentLayerRect APZSampler::GetCompositionBounds( const LayersId& aLayersId, const ScrollableLayerGuid::ViewID& aScrollId, const MutexAutoLock& aProofOfMapLock) const { - // This function can get called on the compositor in case of non WebRender - // get called on the sampler thread in case of WebRender. AssertOnSamplerThread(); RefPtr<AsyncPanZoomController> apzc = mApz->GetTargetAPZC(aLayersId, aScrollId, aProofOfMapLock); if (!apzc) { - // On WebRender it's possible that this function can get called even after - // the target APZC has been already destroyed because destroying the - // animation which triggers this function call is basically processed later - // than the APZC one, i.e. queue mCompositorAnimationsToDelete in - // WebRenderBridgeParent and then remove them in - // WebRenderBridgeParent::RemoveEpochDataPriorTo. + // It's possible that this function can get called even after the target + // APZC has been already destroyed because destroying the animation which + // triggers this function call is basically processed later than the APZC + // one, i.e. queue mCompositorAnimationsToDelete in WebRenderBridgeParent + // and then remove them in WebRenderBridgeParent::RemoveEpochDataPriorTo. return ParentLayerRect(); } diff --git a/gfx/layers/apz/src/APZUtils.h b/gfx/layers/apz/src/APZUtils.h index 6614b6eeae..f36a01f65d 100644 --- a/gfx/layers/apz/src/APZUtils.h +++ b/gfx/layers/apz/src/APZUtils.h @@ -233,6 +233,14 @@ bool AboutToCheckerboard(const FrameMetrics& aPaintedMetrics, */ SideBits GetOverscrollSideBits(const ParentLayerPoint& aOverscrollAmount); +// Represents tri-state when a touch-end event received. +enum class SingleTapState : uint8_t { + NotClick, // The touch-block doesn't trigger a click event + WasClick, // The touch-block did trigger a click event + NotYetDetermined, // It's not yet determined whether the touch-block trigger + // a click event or not since double-tapping might happen +}; + } // namespace apz } // namespace layers diff --git a/gfx/layers/apz/src/AsyncPanZoomController.cpp b/gfx/layers/apz/src/AsyncPanZoomController.cpp index edbd2ecffa..a070340421 100644 --- a/gfx/layers/apz/src/AsyncPanZoomController.cpp +++ b/gfx/layers/apz/src/AsyncPanZoomController.cpp @@ -242,11 +242,6 @@ typedef PlatformSpecificStateBase * Setting this pref to true will cause APZ to handle mouse-dragging of * scrollbar thumbs. * - * \li\b apz.drag.initial.enabled - * Setting this pref to true will cause APZ to try to handle mouse-dragging - * of scrollbar thumbs without an initial round-trip to content to start it - * if possible. Only has an effect if apz.drag.enabled is also true. - * * \li\b apz.drag.touch.enabled * Setting this pref to true will cause APZ to handle touch-dragging of * scrollbar thumbs. Only has an effect if apz.drag.enabled is also true. @@ -1310,10 +1305,13 @@ nsEventStatus AsyncPanZoomController::OnTouchStart( if (RefPtr<GeckoContentController> controller = GetGeckoContentController()) { MOZ_ASSERT(GetCurrentTouchBlock()); - controller->NotifyAPZStateChange( - GetGuid(), APZStateChange::eStartTouch, + const bool canBePanOrZoom = GetCurrentTouchBlock()->GetOverscrollHandoffChain()->CanBePanned( - this), + this) || + (ZoomConstraintsAllowDoubleTapZoom() && + GetCurrentTouchBlock()->TouchActionAllowsDoubleTapZoom()); + controller->NotifyAPZStateChange( + GetGuid(), APZStateChange::eStartTouch, canBePanOrZoom, Some(GetCurrentTouchBlock()->GetBlockId())); } mLastTouch.mTimeStamp = mTouchStartTime = aEvent.mTimeStamp; @@ -3112,7 +3110,7 @@ nsEventStatus AsyncPanZoomController::GenerateSingleTap( // touch block caused a `click` event or not, thus for long-tap events, // it's not necessary. if (aType != TapType::eLongTapUp) { - touch->SetSingleTapOccurred(); + touch->SetSingleTapState(apz::SingleTapState::WasClick); } } // Because this may be being running as part of @@ -3143,7 +3141,7 @@ void AsyncPanZoomController::OnTouchEndOrCancel() { MOZ_ASSERT(GetCurrentTouchBlock()); controller->NotifyAPZStateChange( GetGuid(), APZStateChange::eEndTouch, - GetCurrentTouchBlock()->SingleTapOccurred(), + static_cast<int>(GetCurrentTouchBlock()->SingleTapState()), Some(GetCurrentTouchBlock()->GetBlockId())); } } @@ -3160,6 +3158,21 @@ nsEventStatus AsyncPanZoomController::OnSingleTapUp( return GenerateSingleTap(TapType::eSingleTap, aEvent.mPoint, aEvent.modifiers); } + + // Ignore the event if it does not have valid local coordinates. + // GenerateSingleTap will not send a tap in this case. + if (!ConvertToGecko(aEvent.mPoint)) { + return nsEventStatus_eIgnore; + } + + // Here we need to wait for the call to OnSingleTapConfirmed, we need to tell + // it to ActiveElementManager so that we can do element activation once + // ActiveElementManager got a single tap event later. + if (TouchBlockState* touch = GetCurrentTouchBlock()) { + if (!touch->IsDuringFastFling()) { + touch->SetSingleTapState(apz::SingleTapState::NotYetDetermined); + } + } return nsEventStatus_eIgnore; } @@ -5304,10 +5317,10 @@ void AsyncPanZoomController::UpdateCheckerboardEvent( const MutexAutoLock& aProofOfLock, uint32_t aMagnitude) { if (mCheckerboardEvent && mCheckerboardEvent->RecordFrameInfo(aMagnitude)) { // This checkerboard event is done. Report some metrics to telemetry. - mozilla::glean::gfx_checkerboard::severity.AccumulateSamples( - {mCheckerboardEvent->GetSeverity()}); - mozilla::glean::gfx_checkerboard::peak_pixel_count.AccumulateSamples( - {mCheckerboardEvent->GetPeak()}); + mozilla::glean::gfx_checkerboard::severity.AccumulateSingleSample( + mCheckerboardEvent->GetSeverity()); + mozilla::glean::gfx_checkerboard::peak_pixel_count.AccumulateSingleSample( + mCheckerboardEvent->GetPeak()); mozilla::glean::gfx_checkerboard::duration.AccumulateRawDuration( mCheckerboardEvent->GetDuration()); diff --git a/gfx/layers/apz/src/AsyncPanZoomController.h b/gfx/layers/apz/src/AsyncPanZoomController.h index d0c4537a66..5db831d1bf 100644 --- a/gfx/layers/apz/src/AsyncPanZoomController.h +++ b/gfx/layers/apz/src/AsyncPanZoomController.h @@ -1133,13 +1133,12 @@ class AsyncPanZoomController { UniquePtr<OverscrollEffectBase> mOverscrollEffect; - // Zoom animation id, used for zooming in WebRender. This should only be - // set on the APZC instance for the root content document (i.e. the one we - // support zooming on), and is only used if WebRender is enabled. The - // animation id itself refers to the transform animation id that was set on - // the stacking context in the WR display list. By changing the transform - // associated with this id, we can adjust the scaling that WebRender applies, - // thereby controlling the zoom. + // Zoom animation id, used for zooming. This should only be set on the APZC + // instance for the root content document (i.e. the one we support zooming + // on). The animation id itself refers to the transform animation id that was + // set on the stacking context in the WR display list. By changing the + // transform associated with this id, we can adjust the scaling that WebRender + // applies, thereby controlling the zoom. Maybe<uint64_t> mZoomAnimationId; // Position on screen where user first put their finger down. diff --git a/gfx/layers/apz/src/HitTestingTreeNode.h b/gfx/layers/apz/src/HitTestingTreeNode.h index a4958b1af5..b76f317825 100644 --- a/gfx/layers/apz/src/HitTestingTreeNode.h +++ b/gfx/layers/apz/src/HitTestingTreeNode.h @@ -182,16 +182,16 @@ class HitTestingTreeNode { LayersId mLayersId; - // This is only set if WebRender is enabled, and only for HTTNs - // where IsScrollThumbNode() returns true. It holds the animation id that we - // use to move the thumb node to reflect async scrolling. + // This is only set for HTTNs where IsScrollThumbNode() returns true. It holds + // the animation id that we use to move the thumb node to reflect async + // scrolling. Maybe<uint64_t> mScrollbarAnimationId; // This is set for scrollbar Container and Thumb layers. ScrollbarData mScrollbarData; - // This is only set if WebRender is enabled. It holds the animation id that - // we use to adjust fixed position content for the toolbar. + // This holds the animation id that we use to adjust fixed position content + // for the toolbar. Maybe<uint64_t> mFixedPositionAnimationId; ScrollableLayerGuid::ViewID mFixedPosTarget; @@ -200,8 +200,8 @@ class HitTestingTreeNode { ScrollableLayerGuid::ViewID mStickyPosTarget; LayerRectAbsolute mStickyScrollRangeOuter; LayerRectAbsolute mStickyScrollRangeInner; - // This is only set if WebRender is enabled. It holds the animation id that - // we use to adjust sticky position content for the toolbar. + // This holds the animation id that we use to adjust sticky position content + // for the toolbar. Maybe<uint64_t> mStickyPositionAnimationId; LayerIntRect mVisibleRect; diff --git a/gfx/layers/apz/src/InputBlockState.cpp b/gfx/layers/apz/src/InputBlockState.cpp index 367fbf9e90..785df56c10 100644 --- a/gfx/layers/apz/src/InputBlockState.cpp +++ b/gfx/layers/apz/src/InputBlockState.cpp @@ -16,6 +16,7 @@ #include "mozilla/StaticPrefs_test.h" #include "mozilla/Telemetry.h" // for Telemetry #include "mozilla/ToString.h" +#include "mozilla/layers/APZEventState.h" #include "mozilla/layers/IAPZCTreeManager.h" // for AllowedTouchBehavior #include "OverscrollHandoffState.h" #include "QueuedInput.h" @@ -636,12 +637,12 @@ TouchBlockState::TouchBlockState( : CancelableBlockState(aTargetApzc, aFlags), mAllowedTouchBehaviorSet(false), mDuringFastFling(false), - mSingleTapOccurred(false), mInSlop(false), mForLongTap(false), mLongTapWasProcessed(false), mIsWaitingLongTapResult(false), mNeedsWaitTouchMove(false), + mSingleTapState(apz::SingleTapState::NotClick), mTouchCounter(aCounter), mStartTime(GetTargetApzc()->GetFrameTime().Time()) { mOriginalTargetConfirmedState = mTargetConfirmed; @@ -700,13 +701,12 @@ void TouchBlockState::SetDuringFastFling() { bool TouchBlockState::IsDuringFastFling() const { return mDuringFastFling; } -void TouchBlockState::SetSingleTapOccurred() { - TBS_LOG("%p setting single-tap-occurred flag\n", this); - mSingleTapOccurred = true; +void TouchBlockState::SetSingleTapState(apz::SingleTapState aState) { + TBS_LOG("%p setting single-tap-state: %d\n", this, + static_cast<uint8_t>(aState)); + mSingleTapState = aState; } -bool TouchBlockState::SingleTapOccurred() const { return mSingleTapOccurred; } - bool TouchBlockState::MustStayActive() { // If this touch block is for long-tap, it doesn't need to be active after the // block was processed, it will be taken over by the original touch block diff --git a/gfx/layers/apz/src/InputBlockState.h b/gfx/layers/apz/src/InputBlockState.h index f20a4a5901..d65b1cb57b 100644 --- a/gfx/layers/apz/src/InputBlockState.h +++ b/gfx/layers/apz/src/InputBlockState.h @@ -454,14 +454,12 @@ class TouchBlockState : public CancelableBlockState { */ bool IsDuringFastFling() const; /** - * Set the single-tap-occurred flag that indicates that this touch block - * triggered a single tap event. + * Set the single-tap state flag that indicates that this touch block + * triggered (1) a click, (2) not a click, or (3) not yet sure it will trigger + * a click or not. */ - void SetSingleTapOccurred(); - /** - * @return true iff the single-tap-occurred flag is set on this block. - */ - bool SingleTapOccurred() const; + void SetSingleTapState(apz::SingleTapState aState); + apz::SingleTapState SingleTapState() const { return mSingleTapState; } /** * @return false iff touch-action is enabled and the allowed touch behaviors @@ -537,7 +535,6 @@ class TouchBlockState : public CancelableBlockState { nsTArray<TouchBehaviorFlags> mAllowedTouchBehaviors; bool mAllowedTouchBehaviorSet; bool mDuringFastFling; - bool mSingleTapOccurred; bool mInSlop; // A long tap involves two touch blocks: the original touch // block containing the `touchstart`, and a second one @@ -557,6 +554,7 @@ class TouchBlockState : public CancelableBlockState { // content response for a touch move event. It will be set just before // triggering a long-press event. bool mNeedsWaitTouchMove; + apz::SingleTapState mSingleTapState; ScreenIntPoint mSlopOrigin; // A reference to the InputQueue's touch counter TouchCounter& mTouchCounter; diff --git a/gfx/layers/apz/src/SampledAPZCState.cpp b/gfx/layers/apz/src/SampledAPZCState.cpp index 712a46a3b1..8cdd905aba 100644 --- a/gfx/layers/apz/src/SampledAPZCState.cpp +++ b/gfx/layers/apz/src/SampledAPZCState.cpp @@ -86,6 +86,8 @@ void SampledAPZCState::RemoveFractionalAsyncDelta() { // a snapshot of APZ state (decoupling it from APZ assumptions) and provides // it as an input to the compositor (so all compositor state should be // internally consistent based on this input). + // TODO(bug 1889267): Now that we use WebRender everywhere, can this hack be + // removed? if (mLayoutViewport.TopLeft() == mVisualScrollOffset) { return; } diff --git a/gfx/layers/apz/test/mochitest/helper_bug1669625.html b/gfx/layers/apz/test/mochitest/helper_bug1669625.html index 95d2a4bc2c..4a91c7f0b6 100644 --- a/gfx/layers/apz/test/mochitest/helper_bug1669625.html +++ b/gfx/layers/apz/test/mochitest/helper_bug1669625.html @@ -11,8 +11,7 @@ <script type="application/javascript"> async function test() { - if (SpecialPowers.getBoolPref("apz.force_disable_desktop_zooming_scrollbars") || - getPlatform() == "android") { + if (getPlatform() == "android") { return; } diff --git a/gfx/layers/apz/test/mochitest/helper_bug1806400-2.html b/gfx/layers/apz/test/mochitest/helper_bug1806400-2.html new file mode 100644 index 0000000000..8d2eeca58d --- /dev/null +++ b/gfx/layers/apz/test/mochitest/helper_bug1806400-2.html @@ -0,0 +1,58 @@ +<!DOCTYPE html> +<html> +<meta name="viewport" content="width=device-width; initial-scale=0.4"> +<title>Tests that double-tap-to-zoom never activates elements inside a scrollable container</title> +<script src="/tests/SimpleTest/SimpleTest.js"></script> + <script src="/tests/SimpleTest/paint_listener.js"></script> +<script src="apz_test_utils.js"></script> +<script src="apz_test_native_event_utils.js"></script> +<style> +#scrollable { + height: 50vh; + width: 50vw; + background: yellow; + overflow: scroll; +} + +#scrollabletarget { + height: 200vh; + width: 200vh; + background: green; +} + +#scrollabletarget:active { + background: red; +} + +</style> +<div id="scrollable"> + <div id="scrollabletarget"> + </div> +</div> +<script> +async function test() { + ok(!scrollabletarget.matches(":active"), "should not be active initially"); + + let rAFID = requestAnimationFrame(function ensureInactive() { + let isActive = scrollabletarget.matches(":active"); + ok(!isActive, "Element activation should never happen!"); + if (!isActive) { + rAFID = requestAnimationFrame(ensureInactive); + } + }); + + await doubleTapOn(scrollabletarget, 50, 50, false /* useTouchpad */); + + cancelAnimationFrame(rAFID); +} + +if (getPlatform() != "mac" && getPlatform() != "android") { + ok(true, "Skipping test because double-tap-zoom isn't allowed on " + getPlatform()); + subtestDone(); +} else { + waitUntilApzStable() + .then(test) + .then(subtestDone, subtestFailed); +} +</script> +</html> diff --git a/gfx/layers/apz/test/mochitest/helper_bug1806400-3.html b/gfx/layers/apz/test/mochitest/helper_bug1806400-3.html new file mode 100644 index 0000000000..2e5398119c --- /dev/null +++ b/gfx/layers/apz/test/mochitest/helper_bug1806400-3.html @@ -0,0 +1,49 @@ +<!DOCTYPE html> +<html> +<meta name="viewport" content="width=device-width; initial-scale=1.0"> +<title>Tests that :active state is changed on a scrollable container without any touch event listeners</title> +<script src="/tests/SimpleTest/SimpleTest.js"></script> + <script src="/tests/SimpleTest/paint_listener.js"></script> +<script src="apz_test_utils.js"></script> +<script src="apz_test_native_event_utils.js"></script> +<style> +#scrollable { + height: 50vh; + width: 50vw; + background: yellow; + overflow: scroll; +} + +#scrollabletarget { + height: 200vh; + width: 200vh; + background: green; +} + +#scrollabletarget:active { + background: red; +} + +</style> +<div id="scrollable"> + <div id="scrollabletarget"> + </div> +</div> +<script> +async function test() { + ok(!scrollabletarget.matches(":active"), "should not be active initially"); + + await synthesizeNativeTap(scrollabletarget, 50, 50); + + // In JS there's no way to ensure `APZStateChange::eStartTouch` notification + // has been processed. So we wait for `:active` state change here. + await SimpleTest.promiseWaitForCondition( + () => scrollabletarget.matches(":active"), + "Waiting for :active state change"); + ok(scrollabletarget.matches(":active"), "should be active"); +} +waitUntilApzStable() +.then(test) +.then(subtestDone, subtestFailed); +</script> +</html> diff --git a/gfx/layers/apz/test/mochitest/helper_bug1806400-4.html b/gfx/layers/apz/test/mochitest/helper_bug1806400-4.html new file mode 100644 index 0000000000..46fa95b651 --- /dev/null +++ b/gfx/layers/apz/test/mochitest/helper_bug1806400-4.html @@ -0,0 +1,49 @@ +<!DOCTYPE html> +<html> +<meta name="viewport" content="width=device-width; initial-scale=0.4"> +<title>Tests that double-tap-to-zoom never activates elements inside non scrollable container</title> +<script src="/tests/SimpleTest/SimpleTest.js"></script> + <script src="/tests/SimpleTest/paint_listener.js"></script> +<script src="apz_test_utils.js"></script> +<script src="apz_test_native_event_utils.js"></script> +<style> +#nonscrollabletarget { + height: 300px; + width: 300px; + background: green; +} + +#nonscrollabletarget:active { + background: red; +} + +</style> +<div id="nonscrollabletarget"> +</div> +<script> +async function test() { + ok(!nonscrollabletarget.matches(":active"), "should not be active initially"); + + let rAFID = requestAnimationFrame(function ensureInactive() { + let isActive = nonscrollabletarget.matches(":active"); + ok(!isActive, "Element activation should never happen!"); + if (!isActive) { + rAFID = requestAnimationFrame(ensureInactive); + } + }); + + await doubleTapOn(nonscrollabletarget, 50, 50, false /* useTouchpad */); + + cancelAnimationFrame(rAFID); +} + +if (getPlatform() != "mac" && getPlatform() != "android") { + ok(true, "Skipping test because double-tap-zoom isn't allowed on " + getPlatform()); + subtestDone(); +} else { + waitUntilApzStable() + .then(test) + .then(subtestDone, subtestFailed); +} +</script> +</html> diff --git a/gfx/layers/apz/test/mochitest/helper_bug1806400.html b/gfx/layers/apz/test/mochitest/helper_bug1806400.html new file mode 100644 index 0000000000..03be0c8535 --- /dev/null +++ b/gfx/layers/apz/test/mochitest/helper_bug1806400.html @@ -0,0 +1,54 @@ +<!DOCTYPE html> +<html> +<meta name="viewport" content="width=device-width; initial-scale=1.0"> +<title>Tests that :active state is changed with `touchstart` event listener</title> +<script src="/tests/SimpleTest/SimpleTest.js"></script> + <script src="/tests/SimpleTest/paint_listener.js"></script> +<script src="apz_test_utils.js"></script> +<script src="apz_test_native_event_utils.js"></script> +<style> + #button { + width: 100px; + height: 100px; + } +</style> +<button id="button">Button</button> +<script> +async function test() { + // Set up an active touchstart event listner. + let eventPromise = promiseOneEvent(document.documentElement, "touchstart"); + await promiseApzFlushedRepaints(); + + await synthesizeNativeTouch(button, 10, 10, SpecialPowers.DOMWindowUtils.TOUCH_CONTACT); + await eventPromise; + + // In JS there's no way to ensure `APZStateChange::eStartTouch` notification + // has been processed. So we wait for `:active` state change here. + await SimpleTest.promiseWaitForCondition( + () => button.matches(":active"), + "Waiting for :active state change"); + ok(button.matches(":active"), "should be active"); + + eventPromise = promiseOneEvent(button, "touchend"); + await synthesizeNativeTouch(button, 10, 10, SpecialPowers.DOMWindowUtils.TOUCH_REMOVE); + await eventPromise; + + // Same as above. We need to wait for not `:active` state here. + await SimpleTest.promiseWaitForCondition( + () => !button.matches(":active"), + "Waiting for :active state change"); + ok(!button.matches(":active"), "should not be active"); +} + +if (getPlatform() == "windows") { + // Bug 1875916. On Windows synthesizeNativeTouch(TOUCH_REMOVE) causes + // `InjectTouchInput failure` with ERROR_TIMEOUT. + ok(true, "Test doesn't need to run on Windows"); + subtestDone(); +} else { + waitUntilApzStable() + .then(test) + .then(subtestDone, subtestFailed); +} +</script> +</html> diff --git a/gfx/layers/apz/test/mochitest/helper_hittest_iframe_perspective-4.html b/gfx/layers/apz/test/mochitest/helper_hittest_iframe_perspective-4.html new file mode 100644 index 0000000000..96ea0d3b09 --- /dev/null +++ b/gfx/layers/apz/test/mochitest/helper_hittest_iframe_perspective-4.html @@ -0,0 +1,86 @@ +<!DOCTYPE html> +<html> +<!-- +https://bugzilla.mozilla.org/show_bug.cgi?id=1888904 +--> +<head> + <meta charset="utf-8"> + <meta name="viewport" content="width=device-width; initial-scale=1.0"> + <title>Test that events are delivered with correct coordinates to an iframe inide a no-op perspective transform</title> + <script src="apz_test_native_event_utils.js"></script> + <script src="apz_test_utils.js"></script> + <script src="/tests/SimpleTest/paint_listener.js"></script> + <style> + html, body { + margin: 0; + padding: 0; + } + iframe { + border: 0; + background-color: blue; + } + .modal-dialog { + position: absolute; + top: 500px; + left: 500px; + transform: translate(-50%, -50%); + border: 1px solid black; + } + .item { + perspective: 1000px; + transform: translate3d(0, 0, 0); + } + .g-recaptcha { + transform-origin: 0 0; + transform: scale(0.91); + } + </style> +</head> +<body> + <div class="modal-dialog"> + <div class="item"> + <div class="g-recaptcha"> + <iframe id="iframe" src="https://example.com/tests/gfx/layers/apz/test/mochitest/helper_hittest_iframe_perspective_child.html"></iframe> + </div> + </div> + </div> + </div> + </div> + <script type="application/javascript"> +async function test() { + // Wait for iframe to receive content transforms. + await SpecialPowers.spawn(iframe, [], async () => { + await SpecialPowers.contentTransformsReceived(content.window); + }); + + let clickCoordsInChild = { + offsetX: 0, + offsetY: 0 + }; + let childMessagePromise = new Promise(resolve => { + window.addEventListener("message", event => { + let data = JSON.parse(event.data); + if ("type" in data && data.type == "got-mouse-down") { + clickCoordsInChild = data.coords; + resolve(); + } + }) + }); + await synthesizeNativeMouseEventWithAPZ({ + type: "click", + target: iframe, + offsetX: 100, + offsetY: 100 + }); + await childMessagePromise; + is(clickCoordsInChild.offsetX, 110 /* 100 / 0.91 */, "x coordinate is correct"); + is(clickCoordsInChild.offsetY, 110 /* 100 / 0.91 */, "y coordinate is correct"); +} + +waitUntilApzStable() +.then(test) +.then(subtestDone, subtestFailed); + + </script> +</body> +</html> diff --git a/gfx/layers/apz/test/mochitest/helper_hittest_pointerevents_svg.html b/gfx/layers/apz/test/mochitest/helper_hittest_pointerevents_svg.html index 22b880736d..3b8a7cef3e 100644 --- a/gfx/layers/apz/test/mochitest/helper_hittest_pointerevents_svg.html +++ b/gfx/layers/apz/test/mochitest/helper_hittest_pointerevents_svg.html @@ -132,10 +132,10 @@ async function test() { `bottom left of scroller in testcase ${testId}`); } - // With the first two cases (circle masks) both WebRender and non-WebRender - // emit dispatch-to-content regions for the right side, so for now we just - // test for that. Eventually WebRender should be able to stop emitting DTC - // and we can update this test to be more precise in that case. + // With the first two cases (circle masks) WebRender emits dispatch-to-content + // regions for the right side, so for now we just test for that. + // Eventually WebRender should be able to stop emitting DTC + // and we can update this test to be more precise. // For the two rectangular test cases we get precise results rather than // dispatch-to-content. if (testId == 1 || testId == 2) { diff --git a/gfx/layers/apz/test/mochitest/helper_scrollframe_activation_on_load.html b/gfx/layers/apz/test/mochitest/helper_scrollframe_activation_on_load.html index 1947a89a8f..c3f02d23d9 100644 --- a/gfx/layers/apz/test/mochitest/helper_scrollframe_activation_on_load.html +++ b/gfx/layers/apz/test/mochitest/helper_scrollframe_activation_on_load.html @@ -41,9 +41,8 @@ https://bugzilla.mozilla.org/show_bug.cgi?id=1151663 let config = getHitTestConfig(); let heightMultiplier = SpecialPowers.getCharPref("apz.y_stationary_size_multiplier"); - // With WebRender, the effective height multiplier can be reduced - // for alignment reasons. The reduction should be no more than a - // factor of two. + // The effective height multiplier can be reduced for alignment reasons. + // The reduction should be no more than a factor of two. heightMultiplier /= 2; info("effective displayport height multipler is " + heightMultiplier); diff --git a/gfx/layers/apz/test/mochitest/helper_touch_synthesized_mouseevents.html b/gfx/layers/apz/test/mochitest/helper_touch_synthesized_mouseevents.html index 3930cec3c3..b3d7b4352a 100644 --- a/gfx/layers/apz/test/mochitest/helper_touch_synthesized_mouseevents.html +++ b/gfx/layers/apz/test/mochitest/helper_touch_synthesized_mouseevents.html @@ -76,6 +76,13 @@ async function test() { promiseOneEvent(targetElem, "click"), ]; + // Create a promise for :active state change since in the case where the + // target element is inside a scrollable container, APZ delays :active state + // change, it sometimes happens after all the relavant events above. + const activePromise = SimpleTest.promiseWaitForCondition( + () => targetElem.matches(":active"), + "Waiting for :active state change"); + // Perform a tap gesture await synthesizeNativeTap(targetElem, 50, 50); @@ -88,7 +95,7 @@ async function test() { // The value of ui.touch_activation.duration_ms should be set to // a large value. If we did not delay sending the synthesized // mouse events, this test will not timeout. - await Promise.all(mouseEventPromises); + await Promise.all([...mouseEventPromises, activePromise]); clearTimeout(failTimeout); diff --git a/gfx/layers/apz/test/mochitest/test_group_hittest-3.html b/gfx/layers/apz/test/mochitest/test_group_hittest-3.html index f5675ee790..eac0348b89 100644 --- a/gfx/layers/apz/test/mochitest/test_group_hittest-3.html +++ b/gfx/layers/apz/test/mochitest/test_group_hittest-3.html @@ -33,6 +33,7 @@ var prefs = [ var subtests = [ {"file": "helper_hittest_iframe_perspective.html", "prefs": prefs}, {"file": "helper_hittest_iframe_perspective-3.html", "prefs": prefs}, + {"file": "helper_hittest_iframe_perspective-4.html", "prefs": prefs}, ]; if (isApzEnabled()) { diff --git a/gfx/layers/apz/test/mochitest/test_group_touchevents-5.html b/gfx/layers/apz/test/mochitest/test_group_touchevents-5.html index 0eee77a3ae..e6e4eb40fb 100644 --- a/gfx/layers/apz/test/mochitest/test_group_touchevents-5.html +++ b/gfx/layers/apz/test/mochitest/test_group_touchevents-5.html @@ -9,6 +9,12 @@ <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/> <script type="application/javascript"> +// Increase the tap timeouts so the double-tap is still detected in case of +// random delays during testing. +var doubletap_prefs = [ + ["ui.click_hold_context_menus.delay", 10000], + ["apz.max_tap_time", 10000], +]; var subtests = [ // tests that scrolling doesn't cause extra SchedulePaint calls @@ -23,6 +29,18 @@ var subtests = [ {"file": "helper_bug1719855.html?prevent=contextmenu"}, {"file": "helper_bug1719855.html"}, {"file": "helper_bug1724759.html"}, + {"file": "helper_bug1806400.html", "prefs": [ + // This test uses `SimpleTest.promiseWaitForCondition` which waits for the + // given condition up to 3s, to avoid opening context menu during the time + // span use a longer `ui.click_hold_context_menus.delay` here. + ["ui.click_hold_context_menus.delay", 10000], + ["ui.touch_activation.duration_ms", 1000] + ]}, + {"file": "helper_bug1806400-2.html", "prefs": doubletap_prefs}, + {"file": "helper_bug1806400-3.html", "prefs": [ + ["ui.touch_activation.duration_ms", 90000] + ]}, + {"file": "helper_bug1806400-4.html", "prefs": doubletap_prefs}, // Add new subtests here. If this starts timing out because it's taking too // long, create a test_group_touchevents-6.html file. Refer to 1423011#c57 // for more details. diff --git a/gfx/layers/apz/test/mochitest/test_layerization.html b/gfx/layers/apz/test/mochitest/test_layerization.html index 0ff76de317..e97971b456 100644 --- a/gfx/layers/apz/test/mochitest/test_layerization.html +++ b/gfx/layers/apz/test/mochitest/test_layerization.html @@ -64,9 +64,8 @@ let config = getHitTestConfig(); let activateAllScrollFrames = config.activateAllScrollFrames; let heightMultiplier = SpecialPowers.getCharPref("apz.y_stationary_size_multiplier"); -// With WebRender, the effective height multiplier can be reduced -// for alignment reasons. The reduction should be no more than a -// factor of two. +// The effective height multiplier can be reduced for alignment reasons. +// The reduction should be no more than a factor of two. heightMultiplier /= 2; info("effective displayport height multipler is " + heightMultiplier); diff --git a/gfx/layers/apz/test/mochitest/test_scroll_inactive_bug1190112.html b/gfx/layers/apz/test/mochitest/test_scroll_inactive_bug1190112.html index de54cf93fe..dd18e078b6 100644 --- a/gfx/layers/apz/test/mochitest/test_scroll_inactive_bug1190112.html +++ b/gfx/layers/apz/test/mochitest/test_scroll_inactive_bug1190112.html @@ -523,8 +523,7 @@ async function test() { // Scroll inner again // Tick the refresh driver once to make sure the compositor has sent the // updated scroll offset for the outer scroller to WebRender, so that the - // hit-test in sendWheelAndPaint takes it into account. (This isn't needed - // if using non-WR layers, but doesn't hurt either). + // hit-test in sendWheelAndPaint takes it into account. var dwu = SpecialPowers.getDOMWindowUtils(window); dwu.advanceTimeAndRefresh(16); dwu.restoreNormalRefresh(); diff --git a/gfx/layers/apz/util/APZEventState.cpp b/gfx/layers/apz/util/APZEventState.cpp index 5db6a08429..c205b09ca2 100644 --- a/gfx/layers/apz/util/APZEventState.cpp +++ b/gfx/layers/apz/util/APZEventState.cpp @@ -27,6 +27,7 @@ #include "mozilla/dom/BrowserChild.h" #include "mozilla/dom/MouseEventBinding.h" #include "mozilla/layers/APZCCallbackHelper.h" +#include "mozilla/layers/APZUtils.h" #include "mozilla/layers/IAPZCTreeManager.h" #include "mozilla/widget/nsAutoRollup.h" #include "nsCOMPtr.h" @@ -101,7 +102,7 @@ APZEventState::APZEventState(nsIWidget* aWidget, mContentReceivedInputBlockCallback(std::move(aCallback)), mPendingTouchPreventedResponse(false), mPendingTouchPreventedBlockId(0), - mEndTouchIsClick(false), + mEndTouchState(apz::SingleTapState::NotClick), mFirstTouchCancelled(false), mTouchEndCancelled(false), mReceivedNonTouchStart(false), @@ -349,11 +350,13 @@ void APZEventState::ProcessTouchEvent( case eTouchEnd: if (isTouchPrevented) { mTouchEndCancelled = true; - mEndTouchIsClick = false; + mEndTouchState = apz::SingleTapState::NotClick; } [[fallthrough]]; case eTouchCancel: - mActiveElementManager->HandleTouchEndEvent(mEndTouchIsClick); + if (mActiveElementManager->HandleTouchEndEvent(mEndTouchState)) { + mEndTouchState = apz::SingleTapState::NotClick; + } [[fallthrough]]; case eTouchMove: { if (!mReceivedNonTouchStart) { @@ -515,13 +518,13 @@ void APZEventState::ProcessAPZStateChange(ViewID aViewId, break; } case APZStateChange::eStartTouch: { - bool canBePan = aArg; - mActiveElementManager->HandleTouchStart(canBePan); + bool canBePanOrZoom = aArg; + mActiveElementManager->HandleTouchStart(canBePanOrZoom); // If this is a non-scrollable content, set a timer for the amount of // time specified by ui.touch_activation.duration_ms to clear the // active element state. - APZES_LOG("%s: can-be-pan=%d", __FUNCTION__, aArg); - if (!canBePan) { + APZES_LOG("%s: can-be-pan-or-zoom=%d", __FUNCTION__, aArg); + if (!canBePanOrZoom) { MOZ_ASSERT(aInputBlockId.isSome()); } break; @@ -532,8 +535,10 @@ void APZEventState::ProcessAPZStateChange(ViewID aViewId, break; } case APZStateChange::eEndTouch: { - mEndTouchIsClick = aArg; - mActiveElementManager->HandleTouchEnd(); + mEndTouchState = static_cast<apz::SingleTapState>(aArg); + if (mActiveElementManager->HandleTouchEnd(mEndTouchState)) { + mEndTouchState = apz::SingleTapState::NotClick; + } break; } } diff --git a/gfx/layers/apz/util/APZEventState.h b/gfx/layers/apz/util/APZEventState.h index 52febc0424..3a57e9e6c6 100644 --- a/gfx/layers/apz/util/APZEventState.h +++ b/gfx/layers/apz/util/APZEventState.h @@ -39,6 +39,10 @@ namespace layers { class ActiveElementManager; +namespace apz { +enum class SingleTapState : uint8_t; +} // namespace apz + typedef std::function<void(uint64_t /* input block id */, bool /* prevent default */)> ContentReceivedInputBlockCallback; @@ -106,7 +110,7 @@ class APZEventState final { bool mPendingTouchPreventedResponse; ScrollableLayerGuid mPendingTouchPreventedGuid; uint64_t mPendingTouchPreventedBlockId; - bool mEndTouchIsClick; + apz::SingleTapState mEndTouchState; bool mFirstTouchCancelled; bool mTouchEndCancelled; // Set to true when we have received any one of diff --git a/gfx/layers/apz/util/ActiveElementManager.cpp b/gfx/layers/apz/util/ActiveElementManager.cpp index f2d981e9f0..c92fec783a 100644 --- a/gfx/layers/apz/util/ActiveElementManager.cpp +++ b/gfx/layers/apz/util/ActiveElementManager.cpp @@ -10,6 +10,8 @@ #include "mozilla/StaticPrefs_ui.h" #include "mozilla/dom/Element.h" #include "mozilla/dom/Document.h" +#include "mozilla/layers/APZEventState.h" +#include "mozilla/layers/APZUtils.h" #include "nsITimer.h" static mozilla::LazyLogModule sApzAemLog("apz.activeelement"); @@ -21,7 +23,7 @@ namespace layers { class DelayedClearElementActivation final : public nsITimerCallback, public nsINamed { private: - explicit DelayedClearElementActivation(nsCOMPtr<dom::Element>& aTarget, + explicit DelayedClearElementActivation(RefPtr<dom::Element>& aTarget, const nsCOMPtr<nsITimer>& aTimer) : mTarget(aTarget) // Hold the reference count until we are called back. @@ -33,7 +35,7 @@ class DelayedClearElementActivation final : public nsITimerCallback, NS_DECL_ISUPPORTS static RefPtr<DelayedClearElementActivation> Create( - nsCOMPtr<dom::Element>& aTarget); + RefPtr<dom::Element>& aTarget); NS_IMETHOD Notify(nsITimer*) override; @@ -56,11 +58,12 @@ class DelayedClearElementActivation final : public nsITimerCallback, mTimer = nullptr; } } + dom::Element* GetTarget() const { return mTarget; } private: ~DelayedClearElementActivation() = default; - nsCOMPtr<dom::Element> mTarget; + RefPtr<dom::Element> mTarget; nsCOMPtr<nsITimer> mTimer; bool mProcessedSingleTap; }; @@ -77,7 +80,7 @@ static nsPresContext* GetPresContextFor(nsIContent* aContent) { } RefPtr<DelayedClearElementActivation> DelayedClearElementActivation::Create( - nsCOMPtr<dom::Element>& aTarget) { + RefPtr<dom::Element>& aTarget) { nsCOMPtr<nsITimer> timer = NS_NewTimer(); if (!timer) { return nullptr; @@ -137,7 +140,11 @@ void DelayedClearElementActivation::ClearGlobalActiveContent() { NS_IMPL_ISUPPORTS(DelayedClearElementActivation, nsITimerCallback, nsINamed) ActiveElementManager::ActiveElementManager() - : mCanBePan(false), mCanBePanSet(false), mSetActiveTask(nullptr) {} + : mCanBePanOrZoom(false), + mCanBePanOrZoomSet(false), + mSingleTapBeforeActivation(false), + mSingleTapState(apz::SingleTapState::NotClick), + mSetActiveTask(nullptr) {} ActiveElementManager::~ActiveElementManager() = default; @@ -156,9 +163,9 @@ void ActiveElementManager::SetTargetElement(dom::EventTarget* aTarget) { TriggerElementActivation(); } -void ActiveElementManager::HandleTouchStart(bool aCanBePan) { - AEM_LOG("Touch start, aCanBePan: %d\n", aCanBePan); - if (mCanBePanSet) { +void ActiveElementManager::HandleTouchStart(bool aCanBePanOrZoom) { + AEM_LOG("Touch start, aCanBePanOrZoom: %d\n", aCanBePanOrZoom); + if (mCanBePanOrZoomSet) { // Multiple fingers on screen (since HandleTouchEnd clears mCanBePanSet). AEM_LOG("Multiple fingers on-screen, clearing touch block state\n"); CancelTask(); @@ -167,16 +174,29 @@ void ActiveElementManager::HandleTouchStart(bool aCanBePan) { return; } - mCanBePan = aCanBePan; - mCanBePanSet = true; + mCanBePanOrZoom = aCanBePanOrZoom; + mCanBePanOrZoomSet = true; TriggerElementActivation(); } void ActiveElementManager::TriggerElementActivation() { + // Reset mSingleTapState here either when HandleTouchStart() or + // SetTargetElement() gets called. + // NOTE: It's possible that ProcessSingleTap() gets called in between + // HandleTouchStart() and SetTargetElement() calls. I.e., + // mSingleTapBeforeActivation is true, in such cases it doesn't matter that + // mSingleTapState was reset once and referred it in ProcessSingleTap() and + // then reset here again because in ProcessSingleTap() `NotYetDetermined` is + // the only one state we need to care, and it should NOT happen in the + // scenario. In other words the case where we need to care `NotYetDetermined` + // is when ProcessSingleTap() gets called later than any other events and + // notifications. + mSingleTapState = apz::SingleTapState::NotClick; + // Both HandleTouchStart() and SetTargetElement() call this. They can be - // called in either order. One will set mCanBePanSet, and the other, mTarget. - // We want to actually trigger the activation once both are set. - if (!(mTarget && mCanBePanSet)) { + // called in either order. One will set mCanBePanOrZoomSet, and the other, + // mTarget. We want to actually trigger the activation once both are set. + if (!(mTarget && mCanBePanOrZoomSet)) { return; } @@ -190,10 +210,13 @@ void ActiveElementManager::TriggerElementActivation() { // If the touch cannot be a pan, make mTarget :active right away. // Otherwise, wait a bit to see if the user will pan or not. - if (!mCanBePan) { + if (!mCanBePanOrZoom) { SetActive(mTarget); if (mDelayedClearElementActivation) { + if (mSingleTapBeforeActivation) { + mDelayedClearElementActivation->MarkSingleTapProcessed(); + } mDelayedClearElementActivation->StartTimer(); } } else { @@ -210,6 +233,10 @@ void ActiveElementManager::TriggerElementActivation() { task.forget(), StaticPrefs::ui_touch_activation_delay_ms()); AEM_LOG("Scheduling mSetActiveTask %p\n", mSetActiveTask.get()); } + AEM_LOG( + "Got both touch-end event and end touch notiication, clearing pan " + "state\n"); + mCanBePanOrZoomSet = false; } void ActiveElementManager::ClearActivation() { @@ -218,43 +245,70 @@ void ActiveElementManager::ClearActivation() { ResetActive(); } -void ActiveElementManager::HandleTouchEndEvent(bool aWasClick) { - AEM_LOG("Touch end event, aWasClick: %d\n", aWasClick); +bool ActiveElementManager::HandleTouchEndEvent(apz::SingleTapState aState) { + AEM_LOG("Touch end event, state: %hhu\n", static_cast<uint8_t>(aState)); + + mTouchEndState += TouchEndState::GotTouchEndEvent; + return MaybeChangeActiveState(aState); +} + +bool ActiveElementManager::HandleTouchEnd(apz::SingleTapState aState) { + AEM_LOG("Touch end\n"); + + mTouchEndState += TouchEndState::GotTouchEndNotification; + return MaybeChangeActiveState(aState); +} + +bool ActiveElementManager::MaybeChangeActiveState(apz::SingleTapState aState) { + if (mTouchEndState != + TouchEndStates(TouchEndState::GotTouchEndEvent, + TouchEndState::GotTouchEndNotification)) { + return false; + } - // If the touch was a click, make mTarget :active right away. - // nsEventStateManager will reset the active element when processing - // the mouse-down event generated by the click. CancelTask(); - if (aWasClick) { + + mSingleTapState = aState; + + if (aState == apz::SingleTapState::WasClick) { // Scrollbar thumbs use a different mechanism for their active // highlight (the "active" attribute), so don't set the active state // on them because nothing will clear it. - if (!(mTarget && mTarget->IsXULElement(nsGkAtoms::thumb))) { + if (mCanBePanOrZoom && + !(mTarget && mTarget->IsXULElement(nsGkAtoms::thumb))) { SetActive(mTarget); } } else { - // We might reach here if mCanBePan was false on touch-start and + // We might reach here if mCanBePanOrZoom was false on touch-start and // so we set the element active right away. Now it turns out the // action was not a click so we need to reset the active element. ResetActive(); } ResetTouchBlockState(); -} - -void ActiveElementManager::HandleTouchEnd() { - AEM_LOG("Touch end, clearing pan state\n"); - mCanBePanSet = false; + return true; } void ActiveElementManager::ProcessSingleTap() { if (!mDelayedClearElementActivation) { + // We have not received touch-start notification yet. We will have to run + // MarkSingleTapProcessed() when we receive the touch-start notification. + mSingleTapBeforeActivation = true; return; } + if (mSingleTapState == apz::SingleTapState::NotYetDetermined) { + // If we got `NotYetDetermined`, which means at the moment we don't know for + // sure whether double-tapping will be incoming or not, but now we are sure + // that no double-tapping will happen, thus it's time to activate the target + // element. + if (auto* target = mDelayedClearElementActivation->GetTarget()) { + SetActive(target); + } + } mDelayedClearElementActivation->MarkSingleTapProcessed(); - if (mCanBePan) { + if (mCanBePanOrZoom) { // In the case that we have not started the delayed reset of the element // activation state, start the timer now. mDelayedClearElementActivation->StartTimer(); @@ -297,7 +351,14 @@ void ActiveElementManager::ResetActive() { void ActiveElementManager::ResetTouchBlockState() { mTarget = nullptr; - mCanBePanSet = false; + mCanBePanOrZoomSet = false; + mTouchEndState.clear(); + mSingleTapBeforeActivation = false; + // NOTE: Do not reset mSingleTapState here since it will be necessary in + // ProcessSingleTap() to tell whether we need to activate the target element + // because on environments where double-tap is enabled ProcessSingleTap() + // gets called after both of touch-end event and end touch notiication + // arrived. } void ActiveElementManager::SetActiveTask( diff --git a/gfx/layers/apz/util/ActiveElementManager.h b/gfx/layers/apz/util/ActiveElementManager.h index f8a6f07261..1f2e1e4aad 100644 --- a/gfx/layers/apz/util/ActiveElementManager.h +++ b/gfx/layers/apz/util/ActiveElementManager.h @@ -9,6 +9,7 @@ #include "nsCOMPtr.h" #include "nsISupportsImpl.h" +#include "mozilla/EnumSet.h" namespace mozilla { @@ -23,6 +24,10 @@ namespace layers { class DelayedClearElementActivation; +namespace apz { +enum class SingleTapState : uint8_t; +} // namespace apz + /** * Manages setting and clearing the ':active' CSS pseudostate in the presence * of touch input. @@ -46,9 +51,9 @@ class ActiveElementManager final { /** * Handle a touch-start state notification from APZ. This notification * may be delayed until after touch listeners have responded to the APZ. - * @param aCanBePan whether the touch can be a pan + * @param aCanBePanOrZoom whether the touch can be a pan or double-tap-to-zoom */ - void HandleTouchStart(bool aCanBePan); + void HandleTouchStart(bool aCanBePanOrZoom); /** * Clear the active element. */ @@ -57,12 +62,12 @@ class ActiveElementManager final { * Handle a touch-end or touch-cancel event. * @param aWasClick whether the touch was a click */ - void HandleTouchEndEvent(bool aWasClick); + bool HandleTouchEndEvent(apz::SingleTapState aState); /** * Handle a touch-end state notification from APZ. This notification may be * delayed until after touch listeners have responded to the APZ. */ - void HandleTouchEnd(); + bool HandleTouchEnd(apz::SingleTapState aState); /** * Possibly clear active element sate in response to a single tap. */ @@ -76,17 +81,39 @@ class ActiveElementManager final { /** * The target of the first touch point in the current touch block. */ - nsCOMPtr<dom::Element> mTarget; + RefPtr<dom::Element> mTarget; /** - * Whether the current touch block can be a pan. Set in HandleTouchStart(). + * Whether the current touch block can be a pan or double-tap-to-zoom. Set in + * HandleTouchStart(). */ - bool mCanBePan; + bool mCanBePanOrZoom; /** - * Whether mCanBePan has been set for the current touch block. + * Whether mCanBePanOrZoom has been set for the current touch block. * We need to keep track of this to allow HandleTouchStart() and * SetTargetElement() to be called in either order. */ - bool mCanBePanSet; + bool mCanBePanOrZoomSet; + + bool mSingleTapBeforeActivation; + + enum class TouchEndState : uint8_t { + GotTouchEndNotification, + GotTouchEndEvent, + }; + using TouchEndStates = EnumSet<TouchEndState>; + + /** + * A flag tracks whether `APZStateChange::eEndTouch` notification has arrived + * and whether `eTouchEnd` event has arrived. + */ + TouchEndStates mTouchEndState; + + /** + * A tri-state variable to represent the single tap state when both of + * `APZStateChange::eEndTouch` notification and `eTouchEnd` event arrived. + */ + apz::SingleTapState mSingleTapState; + /** * A task for calling SetActive() after a timeout. */ @@ -103,6 +130,8 @@ class ActiveElementManager final { void ResetTouchBlockState(); void SetActiveTask(const nsCOMPtr<dom::Element>& aTarget); void CancelTask(); + // Returns true if the function changed the active element state. + bool MaybeChangeActiveState(apz::SingleTapState aState); }; } // namespace layers diff --git a/gfx/layers/client/TextureClient.cpp b/gfx/layers/client/TextureClient.cpp index 4187e48955..12a34ff92f 100644 --- a/gfx/layers/client/TextureClient.cpp +++ b/gfx/layers/client/TextureClient.cpp @@ -1546,12 +1546,7 @@ TextureClient::TextureClient(TextureData* aData, TextureFlags aFlags, mUpdated(false), mAddedToCompositableClient(false), mFwdTransactionId(0), - mSerial(++sSerialCounter) -#ifdef GFX_DEBUG_TRACK_CLIENTS_IN_POOL - , - mPoolTracker(nullptr) -#endif -{ + mSerial(++sSerialCounter) { mData->FillInfo(mInfo); mFlags |= mData->GetTextureFlags(); } diff --git a/gfx/layers/client/TextureClient.h b/gfx/layers/client/TextureClient.h index ac0a755698..cf1701e3f0 100644 --- a/gfx/layers/client/TextureClient.h +++ b/gfx/layers/client/TextureClient.h @@ -44,12 +44,6 @@ struct ID3D11Device; namespace mozilla { -// When defined, we track which pool the tile came from and test for -// any inconsistencies. This can be defined in release build as well. -#ifdef DEBUG -# define GFX_DEBUG_TRACK_CLIENTS_IN_POOL 1 -#endif - namespace layers { class AndroidHardwareBufferTextureData; @@ -68,9 +62,6 @@ class GPUVideoTextureData; class TextureClient; class ITextureClientRecycleAllocator; class SharedSurfaceTextureData; -#ifdef GFX_DEBUG_TRACK_CLIENTS_IN_POOL -class TextureClientPool; -#endif class TextureForwarder; struct RemoteTextureOwnerId; @@ -696,11 +687,6 @@ class TextureClient : public AtomicRefCountedWithFinalize<TextureClient> { static void TextureClientRecycleCallback(TextureClient* aClient, void* aClosure); - // Internal helpers for creating texture clients using the actual forwarder - // instead of KnowsCompositor. TextureClientPool uses these to let it cache - // texture clients per-process instead of per ShadowLayerForwarder, but - // everyone else should use the public functions instead. - friend class TextureClientPool; static already_AddRefed<TextureClient> CreateForDrawing( TextureForwarder* aAllocator, gfx::SurfaceFormat aFormat, gfx::IntSize aSize, KnowsCompositor* aKnowsCompositor, @@ -795,12 +781,6 @@ class TextureClient : public AtomicRefCountedWithFinalize<TextureClient> { friend void TestTextureClientYCbCr(TextureClient*, PlanarYCbCrData&); friend already_AddRefed<TextureHost> CreateTextureHostWithBackend( TextureClient*, ISurfaceAllocator*, LayersBackend&); - -#ifdef GFX_DEBUG_TRACK_CLIENTS_IN_POOL - public: - // Pointer to the pool this tile came from. - TextureClientPool* mPoolTracker; -#endif }; /** diff --git a/gfx/layers/client/TextureClientPool.cpp b/gfx/layers/client/TextureClientPool.cpp deleted file mode 100644 index 3eb0c908b6..0000000000 --- a/gfx/layers/client/TextureClientPool.cpp +++ /dev/null @@ -1,307 +0,0 @@ -/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ -/* vim: set ts=8 sts=2 et sw=2 tw=80: */ -/* This Source Code Form is subject to the terms of the Mozilla Public - * License, v. 2.0. If a copy of the MPL was not distributed with this - * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ - -#include "TextureClientPool.h" -#include "CompositableClient.h" -#include "mozilla/layers/CompositableForwarder.h" -#include "mozilla/layers/TextureForwarder.h" -#include "mozilla/StaticPrefs_layers.h" - -#include "nsComponentManagerUtils.h" - -#define TCP_LOG(...) -// #define TCP_LOG(...) printf_stderr(__VA_ARGS__); - -namespace mozilla { -namespace layers { - -// We want to shrink to our maximum size of N unused tiles -// after a timeout to allow for short-term budget requirements -static void ShrinkCallback(nsITimer* aTimer, void* aClosure) { - static_cast<TextureClientPool*>(aClosure)->ShrinkToMaximumSize(); -} - -// After a certain amount of inactivity, let's clear the pool so that -// we don't hold onto tiles needlessly. In general, allocations are -// cheap enough that re-allocating isn't an issue unless we're allocating -// at an inopportune time (e.g. mid-animation). -static void ClearCallback(nsITimer* aTimer, void* aClosure) { - static_cast<TextureClientPool*>(aClosure)->Clear(); -} - -TextureClientPool::TextureClientPool( - KnowsCompositor* aKnowsCompositor, gfx::SurfaceFormat aFormat, - gfx::IntSize aSize, TextureFlags aFlags, uint32_t aShrinkTimeoutMsec, - uint32_t aClearTimeoutMsec, uint32_t aInitialPoolSize, - uint32_t aPoolUnusedSize, TextureForwarder* aAllocator) - : mKnowsCompositor(aKnowsCompositor), - mFormat(aFormat), - mSize(aSize), - mFlags(aFlags), - mShrinkTimeoutMsec(aShrinkTimeoutMsec), - mClearTimeoutMsec(aClearTimeoutMsec), - mInitialPoolSize(aInitialPoolSize), - mPoolUnusedSize(aPoolUnusedSize), - mOutstandingClients(0), - mSurfaceAllocator(aAllocator), - mDestroyed(false) { - TCP_LOG("TexturePool %p created with maximum unused texture clients %u\n", - this, mInitialPoolSize); - mShrinkTimer = NS_NewTimer(); - mClearTimer = NS_NewTimer(); - if (aFormat == gfx::SurfaceFormat::UNKNOWN) { - gfxWarning() << "Creating texture pool for SurfaceFormat::UNKNOWN format"; - } -} - -TextureClientPool::~TextureClientPool() { - mShrinkTimer->Cancel(); - mClearTimer->Cancel(); -} - -#ifdef GFX_DEBUG_TRACK_CLIENTS_IN_POOL -static bool TestClientPool(const char* what, TextureClient* aClient, - TextureClientPool* aPool) { - if (!aClient || !aPool) { - return false; - } - - TextureClientPool* actual = aClient->mPoolTracker; - bool ok = (actual == aPool); - if (ok) { - ok = (aClient->GetFormat() == aPool->GetFormat()); - } - - if (!ok) { - if (actual) { - gfxCriticalError() << "Pool error(" << what << "): " << aPool << "-" - << aPool->GetFormat() << ", " << actual << "-" - << actual->GetFormat() << ", " << aClient->GetFormat(); - MOZ_CRASH("GFX: Crashing with actual"); - } else { - gfxCriticalError() << "Pool error(" << what << "): " << aPool << "-" - << aPool->GetFormat() << ", nullptr, " - << aClient->GetFormat(); - MOZ_CRASH("GFX: Crashing without actual"); - } - } - return ok; -} -#endif - -already_AddRefed<TextureClient> TextureClientPool::GetTextureClient() { - // Try to fetch a client from the pool - RefPtr<TextureClient> textureClient; - - // We initially allocate mInitialPoolSize for our pool. If we run - // out of TextureClients, we allocate additional TextureClients to try and - // keep around mPoolUnusedSize - if (mTextureClients.empty()) { - AllocateTextureClient(); - } - - if (mTextureClients.empty()) { - // All our allocations failed, return nullptr - return nullptr; - } - - mOutstandingClients++; - textureClient = mTextureClients.top(); - mTextureClients.pop(); -#ifdef GFX_DEBUG_TRACK_CLIENTS_IN_POOL - if (textureClient) { - textureClient->mPoolTracker = this; - } - DebugOnly<bool> ok = TestClientPool("fetch", textureClient, this); - MOZ_ASSERT(ok); -#endif - TCP_LOG("TexturePool %p giving %p from pool; size %u outstanding %u\n", this, - textureClient.get(), mTextureClients.size(), mOutstandingClients); - - return textureClient.forget(); -} - -void TextureClientPool::AllocateTextureClient() { - TCP_LOG("TexturePool %p allocating TextureClient, outstanding %u\n", this, - mOutstandingClients); - - TextureAllocationFlags allocFlags = ALLOC_DEFAULT; - - RefPtr<TextureClient> newClient; - if (StaticPrefs::layers_force_shmem_tiles_AtStartup()) { - // gfx::BackendType::NONE means use the content backend - newClient = TextureClient::CreateForRawBufferAccess( - mSurfaceAllocator, mFormat, mSize, gfx::BackendType::NONE, GetBackend(), - mFlags, allocFlags); - } else { - newClient = TextureClient::CreateForDrawing( - mSurfaceAllocator, mFormat, mSize, mKnowsCompositor, - BackendSelector::Content, mFlags, allocFlags); - } - - if (newClient) { - mTextureClients.push(newClient); - } -} - -void TextureClientPool::ResetTimers() { - // Shrink down if we're beyond our maximum size - if (mShrinkTimeoutMsec && - mTextureClients.size() + mTextureClientsDeferred.size() > - mPoolUnusedSize) { - TCP_LOG("TexturePool %p scheduling a shrink-to-max-size\n", this); - mShrinkTimer->InitWithNamedFuncCallback( - ShrinkCallback, this, mShrinkTimeoutMsec, nsITimer::TYPE_ONE_SHOT, - "layers::TextureClientPool::ResetTimers"); - } - - // Clear pool after a period of inactivity to reduce memory consumption - if (mClearTimeoutMsec) { - TCP_LOG("TexturePool %p scheduling a clear\n", this); - mClearTimer->InitWithNamedFuncCallback( - ClearCallback, this, mClearTimeoutMsec, nsITimer::TYPE_ONE_SHOT, - "layers::TextureClientPool::ResetTimers"); - } -} - -void TextureClientPool::ReturnTextureClient(TextureClient* aClient) { - if (!aClient || mDestroyed) { - return; - } -#ifdef GFX_DEBUG_TRACK_CLIENTS_IN_POOL - DebugOnly<bool> ok = TestClientPool("return", aClient, this); - MOZ_ASSERT(ok); -#endif - // Add the client to the pool: - MOZ_ASSERT(mOutstandingClients > mTextureClientsDeferred.size()); - mOutstandingClients--; - mTextureClients.push(aClient); - TCP_LOG("TexturePool %p had client %p returned; size %u outstanding %u\n", - this, aClient, mTextureClients.size(), mOutstandingClients); - - ResetTimers(); -} - -void TextureClientPool::ReturnTextureClientDeferred(TextureClient* aClient) { - if (!aClient || mDestroyed) { - return; - } - MOZ_ASSERT(aClient->HasReadLock()); -#ifdef GFX_DEBUG_TRACK_CLIENTS_IN_POOL - DebugOnly<bool> ok = TestClientPool("defer", aClient, this); - MOZ_ASSERT(ok); -#endif - mTextureClientsDeferred.push_back(aClient); - TCP_LOG( - "TexturePool %p had client %p defer-returned, size %u outstanding %u\n", - this, aClient, mTextureClientsDeferred.size(), mOutstandingClients); - - ResetTimers(); -} - -void TextureClientPool::ShrinkToMaximumSize() { - // We're over our desired maximum size, immediately shrink down to the - // maximum. - // - // We cull from the deferred TextureClients first, as we can't reuse those - // until they get returned. - uint32_t totalUnusedTextureClients = - mTextureClients.size() + mTextureClientsDeferred.size(); - - // If we have > mInitialPoolSize outstanding, then we want to keep around - // mPoolUnusedSize at a maximum. If we have fewer than mInitialPoolSize - // outstanding, then keep around the entire initial pool size. - uint32_t targetUnusedClients; - if (mOutstandingClients > mInitialPoolSize) { - targetUnusedClients = mPoolUnusedSize; - } else { - targetUnusedClients = mInitialPoolSize; - } - - TCP_LOG( - "TexturePool %p shrinking to maximum unused size %u; current pool size " - "%u; total outstanding %u\n", - this, targetUnusedClients, totalUnusedTextureClients, - mOutstandingClients); - - while (totalUnusedTextureClients > targetUnusedClients) { - if (!mTextureClientsDeferred.empty()) { - mOutstandingClients--; - TCP_LOG("TexturePool %p dropped deferred client %p; %u remaining\n", this, - mTextureClientsDeferred.front().get(), - mTextureClientsDeferred.size() - 1); - mTextureClientsDeferred.pop_front(); - } else { - TCP_LOG("TexturePool %p dropped non-deferred client %p; %u remaining\n", - this, mTextureClients.top().get(), mTextureClients.size() - 1); - mTextureClients.pop(); - } - totalUnusedTextureClients--; - } -} - -void TextureClientPool::ReturnDeferredClients() { - if (mTextureClientsDeferred.empty()) { - return; - } - - TCP_LOG("TexturePool %p returning %u deferred clients to pool\n", this, - mTextureClientsDeferred.size()); - - ReturnUnlockedClients(); - ShrinkToMaximumSize(); -} - -void TextureClientPool::ReturnUnlockedClients() { - for (auto it = mTextureClientsDeferred.begin(); - it != mTextureClientsDeferred.end();) { - MOZ_ASSERT((*it)->GetNonBlockingReadLockCount() >= 1); - // Last count is held by the lock itself. - if (!(*it)->IsReadLocked()) { - mTextureClients.push(*it); - it = mTextureClientsDeferred.erase(it); - - MOZ_ASSERT(mOutstandingClients > 0); - mOutstandingClients--; - } else { - it++; - } - } -} - -void TextureClientPool::ReportClientLost() { - MOZ_ASSERT(mOutstandingClients > mTextureClientsDeferred.size()); - mOutstandingClients--; - TCP_LOG("TexturePool %p getting report client lost; down to %u outstanding\n", - this, mOutstandingClients); -} - -void TextureClientPool::Clear() { - TCP_LOG("TexturePool %p getting cleared\n", this); - while (!mTextureClients.empty()) { - TCP_LOG("TexturePool %p releasing client %p\n", this, - mTextureClients.top().get()); - mTextureClients.pop(); - } - while (!mTextureClientsDeferred.empty()) { - MOZ_ASSERT(mOutstandingClients > 0); - mOutstandingClients--; - TCP_LOG("TexturePool %p releasing deferred client %p\n", this, - mTextureClientsDeferred.front().get()); - mTextureClientsDeferred.pop_front(); - } -} - -void TextureClientPool::Destroy() { - Clear(); - mDestroyed = true; - mInitialPoolSize = 0; - mPoolUnusedSize = 0; - mKnowsCompositor = nullptr; -} - -} // namespace layers -} // namespace mozilla diff --git a/gfx/layers/client/TextureClientPool.h b/gfx/layers/client/TextureClientPool.h deleted file mode 100644 index d557ca9a03..0000000000 --- a/gfx/layers/client/TextureClientPool.h +++ /dev/null @@ -1,175 +0,0 @@ -/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ -/* vim: set ts=8 sts=2 et sw=2 tw=80: */ -/* This Source Code Form is subject to the terms of the Mozilla Public - * License, v. 2.0. If a copy of the MPL was not distributed with this - * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ - -#ifndef MOZILLA_GFX_TEXTURECLIENTPOOL_H -#define MOZILLA_GFX_TEXTURECLIENTPOOL_H - -#include "mozilla/gfx/Types.h" -#include "mozilla/gfx/Point.h" -#include "mozilla/RefPtr.h" -#include "mozilla/layers/KnowsCompositor.h" -#include "TextureClient.h" -#include "nsITimer.h" -#include <stack> -#include <list> - -namespace mozilla { -namespace layers { - -class ISurfaceAllocator; -class TextureForwarder; -class TextureReadLock; - -class TextureClientAllocator { - protected: - virtual ~TextureClientAllocator() = default; - - public: - NS_INLINE_DECL_REFCOUNTING(TextureClientAllocator) - - virtual already_AddRefed<TextureClient> GetTextureClient() = 0; - - /** - * Return a TextureClient that is not yet ready to be reused, but will be - * imminently. - */ - virtual void ReturnTextureClientDeferred(TextureClient* aClient) = 0; - - virtual void ReportClientLost() = 0; -}; - -class TextureClientPool final : public TextureClientAllocator { - virtual ~TextureClientPool(); - - public: - TextureClientPool(KnowsCompositor* aKnowsCompositor, - gfx::SurfaceFormat aFormat, gfx::IntSize aSize, - TextureFlags aFlags, uint32_t aShrinkTimeoutMsec, - uint32_t aClearTimeoutMsec, uint32_t aInitialPoolSize, - uint32_t aPoolUnusedSize, TextureForwarder* aAllocator); - - /** - * Gets an allocated TextureClient of size and format that are determined - * by the initialisation parameters given to the pool. This will either be - * a cached client that was returned to the pool, or a newly allocated - * client if one isn't available. - * - * All clients retrieved by this method should be returned using the return - * functions, or reported lost so that the pool can manage its size correctly. - */ - already_AddRefed<TextureClient> GetTextureClient() override; - - /** - * Return a TextureClient that is no longer being used and is ready for - * immediate re-use or destruction. - */ - void ReturnTextureClient(TextureClient* aClient); - - /** - * Return a TextureClient that is not yet ready to be reused, but will be - * imminently. - */ - void ReturnTextureClientDeferred(TextureClient* aClient) override; - - /** - * Return any clients to the pool that were previously returned in - * ReturnTextureClientDeferred. - */ - void ReturnDeferredClients(); - - /** - * Attempt to shrink the pool so that there are no more than - * mInitialPoolSize outstanding. - */ - void ShrinkToMaximumSize(); - - /** - * Report that a client retrieved via GetTextureClient() has become - * unusable, so that it will no longer be tracked. - */ - void ReportClientLost() override; - - /** - * Calling this will cause the pool to attempt to relinquish any unused - * clients. - */ - void Clear(); - - LayersBackend GetBackend() const { - return mKnowsCompositor->GetCompositorBackendType(); - } - int32_t GetMaxTextureSize() const { - return mKnowsCompositor->GetMaxTextureSize(); - } - gfx::SurfaceFormat GetFormat() { return mFormat; } - TextureFlags GetFlags() const { return mFlags; } - - /** - * Clear the pool and put it in a state where it won't recycle any new - * texture. - */ - void Destroy(); - - private: - void ReturnUnlockedClients(); - - /// Allocate a single TextureClient to be returned from the pool. - void AllocateTextureClient(); - - /// Reset and/or initialise timers for shrinking/clearing the pool. - void ResetTimers(); - - /// KnowsCompositor passed to the TextureClient for buffer creation. - RefPtr<KnowsCompositor> mKnowsCompositor; - - /// Format is passed to the TextureClient for buffer creation. - gfx::SurfaceFormat mFormat; - - /// The width and height of the tiles to be used. - gfx::IntSize mSize; - - /// Flags passed to the TextureClient for buffer creation. - const TextureFlags mFlags; - - /// How long to wait after a TextureClient is returned before trying - /// to shrink the pool to its maximum size of mPoolUnusedSize. - uint32_t mShrinkTimeoutMsec; - - /// How long to wait after a TextureClient is returned before trying - /// to clear the pool. - uint32_t mClearTimeoutMsec; - - // The initial number of unused texture clients to seed the pool with - // on construction - uint32_t mInitialPoolSize; - - // How many unused texture clients to try and keep around if we go over - // the initial allocation - uint32_t mPoolUnusedSize; - - /// This is a total number of clients in the wild and in the stack of - /// deferred clients (see below). So, the total number of clients in - /// existence is always mOutstandingClients + the size of mTextureClients. - uint32_t mOutstandingClients; - - std::stack<RefPtr<TextureClient>> mTextureClients; - - std::list<RefPtr<TextureClient>> mTextureClientsDeferred; - RefPtr<nsITimer> mShrinkTimer; - RefPtr<nsITimer> mClearTimer; - // This mSurfaceAllocator owns us, so no need to hold a ref to it - TextureForwarder* mSurfaceAllocator; - - // Keep track of whether this pool has been destroyed or not. If it has, - // we won't accept returns of TextureClients anymore, and the refcounting - // should take care of their destruction. - bool mDestroyed; -}; - -} // namespace layers -} // namespace mozilla - -#endif /* MOZILLA_GFX_TEXTURECLIENTPOOL_H */ diff --git a/gfx/layers/client/TextureRecorded.cpp b/gfx/layers/client/TextureRecorded.cpp index da4ca4f8f3..b3596efdc3 100644 --- a/gfx/layers/client/TextureRecorded.cpp +++ b/gfx/layers/client/TextureRecorded.cpp @@ -33,7 +33,7 @@ RecordedTextureData::~RecordedTextureData() { // We need the translator to drop its reference for the DrawTarget first, // because the TextureData might need to destroy its DrawTarget within a lock. mSnapshot = nullptr; - mSnapshotWrapper = nullptr; + DetachSnapshotWrapper(); mDT = nullptr; mCanvasChild->CleanupTexture(mTextureId); mCanvasChild->RecordEvent(RecordedTextureDestruction( @@ -92,10 +92,25 @@ bool RecordedTextureData::Lock(OpenMode aMode) { return true; } +void RecordedTextureData::DetachSnapshotWrapper(bool aInvalidate, + bool aRelease) { + if (mSnapshotWrapper) { + // If the snapshot only has one ref, then we don't need to worry about + // copying before invalidation since it is about to be deleted. Otherwise, + // we need to ensure any internal data is appropriately copied before + // shmems are potentially overwritten if there are still existing users. + mCanvasChild->DetachSurface(mSnapshotWrapper, + aInvalidate && !mSnapshotWrapper->hasOneRef()); + if (aRelease) { + mSnapshotWrapper = nullptr; + } + } +} + void RecordedTextureData::Unlock() { if ((mLockedMode == OpenMode::OPEN_READ_WRITE) && mCanvasChild->ShouldCacheDataSurface()) { - mSnapshotWrapper = nullptr; + DetachSnapshotWrapper(); mSnapshot = mDT->Snapshot(); mDT->DetachAllSnapshots(); mCanvasChild->RecordEvent(RecordedCacheDataSurface(mSnapshot.get())); @@ -108,11 +123,9 @@ void RecordedTextureData::Unlock() { already_AddRefed<gfx::DrawTarget> RecordedTextureData::BorrowDrawTarget() { if (mLockedMode & OpenMode::OPEN_WRITE) { + // The snapshot will be invalidated. mSnapshot = nullptr; - if (mSnapshotWrapper) { - mCanvasChild->DetachSurface(mSnapshotWrapper); - mSnapshotWrapper = nullptr; - } + DetachSnapshotWrapper(true); } return do_AddRef(mDT); } @@ -122,18 +135,22 @@ void RecordedTextureData::EndDraw() { MOZ_ASSERT(mLockedMode == OpenMode::OPEN_READ_WRITE); if (mCanvasChild->ShouldCacheDataSurface()) { - mSnapshotWrapper = nullptr; + DetachSnapshotWrapper(); mSnapshot = mDT->Snapshot(); mCanvasChild->RecordEvent(RecordedCacheDataSurface(mSnapshot.get())); } } already_AddRefed<gfx::SourceSurface> RecordedTextureData::BorrowSnapshot() { - if (mSnapshotWrapper && (!mDT || !mDT->IsDirty())) { - // The DT is unmodified since the last time snapshot was borrowed, so it - // is safe to reattach the snapshot for shmem readbacks. - mCanvasChild->AttachSurface(mSnapshotWrapper); - return do_AddRef(mSnapshotWrapper); + if (mSnapshotWrapper) { + if (!mDT || !mDT->IsDirty()) { + // The DT is unmodified since the last time snapshot was borrowed, so it + // is safe to reattach the snapshot for shmem readbacks. + mCanvasChild->AttachSurface(mSnapshotWrapper); + return do_AddRef(mSnapshotWrapper); + } + + DetachSnapshotWrapper(); } // There are some failure scenarios where we have no DrawTarget and @@ -153,9 +170,10 @@ already_AddRefed<gfx::SourceSurface> RecordedTextureData::BorrowSnapshot() { void RecordedTextureData::ReturnSnapshot( already_AddRefed<gfx::SourceSurface> aSnapshot) { RefPtr<gfx::SourceSurface> snapshot = aSnapshot; - if (mSnapshotWrapper) { - mCanvasChild->DetachSurface(mSnapshotWrapper); - } + // The snapshot needs to be marked detached but we keep the wrapper around + // so that it can be reused without repeatedly creating it and accidentally + // reading back data for each new instantiation. + DetachSnapshotWrapper(false, false); } void RecordedTextureData::Deallocate(LayersIPCChannel* aAllocator) {} diff --git a/gfx/layers/client/TextureRecorded.h b/gfx/layers/client/TextureRecorded.h index 56e504fb54..9e4e69e78d 100644 --- a/gfx/layers/client/TextureRecorded.h +++ b/gfx/layers/client/TextureRecorded.h @@ -58,6 +58,8 @@ class RecordedTextureData final : public TextureData { ~RecordedTextureData() override; + void DetachSnapshotWrapper(bool aInvalidate = false, bool aRelease = true); + int64_t mTextureId; RefPtr<CanvasChild> mCanvasChild; gfx::IntSize mSize; diff --git a/gfx/layers/ipc/CanvasChild.cpp b/gfx/layers/ipc/CanvasChild.cpp index 515463cd8e..a25d5e6799 100644 --- a/gfx/layers/ipc/CanvasChild.cpp +++ b/gfx/layers/ipc/CanvasChild.cpp @@ -133,6 +133,16 @@ class SourceSurfaceCanvasRecording final : public gfx::SourceSurface { void AttachSurface() { mDetached = false; } void DetachSurface() { mDetached = true; } + void InvalidateDataSurface() { + if (mDataSourceSurface && mMayInvalidate) { + // This must be the only reference to the data left. + MOZ_ASSERT(mDataSourceSurface->hasOneRef()); + mDataSourceSurface = + gfx::Factory::CopyDataSourceSurface(mDataSourceSurface); + mMayInvalidate = false; + } + } + already_AddRefed<gfx::SourceSurface> ExtractSubrect( const gfx::IntRect& aRect) final { return mRecordedSurface->ExtractSubrect(aRect); @@ -142,8 +152,8 @@ class SourceSurfaceCanvasRecording final : public gfx::SourceSurface { void EnsureDataSurfaceOnMainThread() { // The data can only be retrieved on the main thread. if (!mDataSourceSurface && NS_IsMainThread()) { - mDataSourceSurface = - mCanvasChild->GetDataSurface(mTextureId, mRecordedSurface, mDetached); + mDataSourceSurface = mCanvasChild->GetDataSurface( + mTextureId, mRecordedSurface, mDetached, mMayInvalidate); } } @@ -167,6 +177,7 @@ class SourceSurfaceCanvasRecording final : public gfx::SourceSurface { RefPtr<CanvasDrawEventRecorder> mRecorder; RefPtr<gfx::DataSourceSurface> mDataSourceSurface; bool mDetached = false; + bool mMayInvalidate = false; }; class CanvasDataShmemHolder { @@ -420,6 +431,7 @@ already_AddRefed<gfx::DrawTargetRecording> CanvasChild::CreateDrawTarget( gfx::BackendType::SKIA, gfx::IntSize(1, 1), aFormat); RefPtr<gfx::DrawTargetRecording> dt = MakeAndAddRef<gfx::DrawTargetRecording>( mRecorder, aTextureId, aTextureOwnerId, dummyDt, aSize); + dt->SetOptimizeTransform(true); mTextureInfo.insert({aTextureId, {}}); @@ -483,7 +495,8 @@ int64_t CanvasChild::CreateCheckpoint() { } already_AddRefed<gfx::DataSourceSurface> CanvasChild::GetDataSurface( - int64_t aTextureId, const gfx::SourceSurface* aSurface, bool aDetached) { + int64_t aTextureId, const gfx::SourceSurface* aSurface, bool aDetached, + bool& aMayInvalidate) { NS_ASSERT_OWNINGTHREAD(CanvasChild); MOZ_ASSERT(aSurface); @@ -527,6 +540,7 @@ already_AddRefed<gfx::DataSourceSurface> CanvasChild::GetDataSurface( gfx::Factory::CreateWrappingDataSourceSurface( shmemPtr, stride, ssSize, ssFormat, ReleaseDataShmemHolder, closure); + aMayInvalidate = true; return dataSurface.forget(); } } @@ -556,6 +570,7 @@ already_AddRefed<gfx::DataSourceSurface> CanvasChild::GetDataSurface( RefPtr<gfx::DataSourceSurface> dataSurface = gfx::Factory::CreateWrappingDataSourceSurface( data, stride, ssSize, ssFormat, ReleaseDataShmemHolder, closure); + aMayInvalidate = false; return dataSurface.forget(); } @@ -593,10 +608,14 @@ void CanvasChild::AttachSurface(const RefPtr<gfx::SourceSurface>& aSurface) { } } -void CanvasChild::DetachSurface(const RefPtr<gfx::SourceSurface>& aSurface) { +void CanvasChild::DetachSurface(const RefPtr<gfx::SourceSurface>& aSurface, + bool aInvalidate) { if (auto* surface = static_cast<SourceSurfaceCanvasRecording*>(aSurface.get())) { surface->DetachSurface(); + if (aInvalidate) { + surface->InvalidateDataSurface(); + } } } diff --git a/gfx/layers/ipc/CanvasChild.h b/gfx/layers/ipc/CanvasChild.h index e22109f406..a0cf22b0ec 100644 --- a/gfx/layers/ipc/CanvasChild.h +++ b/gfx/layers/ipc/CanvasChild.h @@ -22,7 +22,7 @@ class ThreadSafeWorkerRef; namespace gfx { class DrawTargetRecording; class SourceSurface; -} +} // namespace gfx namespace layers { class CanvasDrawEventRecorder; @@ -132,7 +132,8 @@ class CanvasChild final : public PCanvasChild, public SupportsWeakPtr { /** * The DrawTargetRecording is about to change, so detach the old snapshot. */ - void DetachSurface(const RefPtr<gfx::SourceSurface>& aSurface); + void DetachSurface(const RefPtr<gfx::SourceSurface>& aSurface, + bool aInvalidate = false); /** * Get DataSourceSurface from the translated equivalent version of aSurface in @@ -141,11 +142,13 @@ class CanvasChild final : public PCanvasChild, public SupportsWeakPtr { * @param aSurface the SourceSurface in this process for which we need a * DataSourceSurface * @param aDetached whether the surface is old + * @param aMayInvalidate whether the data may be invalidated by future changes * @returns a DataSourceSurface created from data for aSurface retrieve from * GPU process */ already_AddRefed<gfx::DataSourceSurface> GetDataSurface( - int64_t aTextureId, const gfx::SourceSurface* aSurface, bool aDetached); + int64_t aTextureId, const gfx::SourceSurface* aSurface, bool aDetached, + bool& aMayInvalidate); bool RequiresRefresh(int64_t aTextureId) const; diff --git a/gfx/layers/ipc/CanvasTranslator.cpp b/gfx/layers/ipc/CanvasTranslator.cpp index 4a184f48d8..3fd7a4c4c4 100644 --- a/gfx/layers/ipc/CanvasTranslator.cpp +++ b/gfx/layers/ipc/CanvasTranslator.cpp @@ -21,6 +21,7 @@ #include "mozilla/layers/ImageDataSerializer.h" #include "mozilla/layers/SharedSurfacesParent.h" #include "mozilla/layers/TextureClient.h" +#include "mozilla/layers/VideoBridgeParent.h" #include "mozilla/StaticPrefs_gfx.h" #include "mozilla/SyncRunnable.h" #include "mozilla/TaskQueue.h" @@ -112,6 +113,8 @@ static bool CreateAndMapShmem(RefPtr<ipc::SharedMemoryBasic>& aShmem, return true; } +StaticRefPtr<gfx::SharedContextWebgl> CanvasTranslator::sSharedContext; + bool CanvasTranslator::EnsureSharedContextWebgl() { if (!mSharedContext || mSharedContext->IsContextLost()) { if (mSharedContext) { @@ -121,7 +124,14 @@ bool CanvasTranslator::EnsureSharedContextWebgl() { mRemoteTextureOwner->ClearRecycledTextures(); } } - mSharedContext = gfx::SharedContextWebgl::Create(); + // Check if the global shared context is still valid. If not, instantiate + // a new one before we try to use it. + if (!sSharedContext || sSharedContext->IsContextLost()) { + sSharedContext = gfx::SharedContextWebgl::Create(); + } + mSharedContext = sSharedContext; + // If we can't get a new context, then the only thing left to do is block + // new canvases. if (!mSharedContext || mSharedContext->IsContextLost()) { mSharedContext = nullptr; BlockCanvas(); @@ -131,6 +141,13 @@ bool CanvasTranslator::EnsureSharedContextWebgl() { return true; } +void CanvasTranslator::Shutdown() { + if (sSharedContext) { + gfx::CanvasRenderThread::Dispatch(NS_NewRunnableFunction( + "CanvasTranslator::Shutdown", []() { sSharedContext = nullptr; })); + } +} + mozilla::ipc::IPCResult CanvasTranslator::RecvInitTranslator( TextureType aTextureType, TextureType aWebglTextureType, gfx::BackendType aBackendType, Handle&& aReadHandle, @@ -1144,6 +1161,13 @@ void CanvasTranslator::ClearTextureInfo() { mTextureInfo.clear(); mDrawTargets.Clear(); mSharedContext = nullptr; + // If the global shared context's ref is the last ref left, then clear out + // any internal caches and textures from the context, but still keep it + // alive. This saves on startup costs while not contributing significantly + // to memory usage. + if (sSharedContext && sSharedContext->hasOneRef()) { + sSharedContext->ClearCaches(); + } mBaseDT = nullptr; if (mReferenceTextureData) { mReferenceTextureData->Unlock(); @@ -1163,6 +1187,46 @@ already_AddRefed<gfx::SourceSurface> CanvasTranslator::LookupExternalSurface( return mSharedSurfacesHolder->Get(wr::ToExternalImageId(aKey)); } +// Check if the surface descriptor describes a GPUVideo texture for which we +// only have an opaque source/handle from SurfaceDescriptorRemoteDecoder to +// derive the actual texture from. +static bool SDIsNullRemoteDecoder(const SurfaceDescriptor& sd) { + return sd.type() == SurfaceDescriptor::TSurfaceDescriptorGPUVideo && + sd.get_SurfaceDescriptorGPUVideo() + .get_SurfaceDescriptorRemoteDecoder() + .subdesc() + .type() == RemoteDecoderVideoSubDescriptor::Tnull_t; +} + +already_AddRefed<gfx::SourceSurface> +CanvasTranslator::LookupSourceSurfaceFromSurfaceDescriptor( + const SurfaceDescriptor& aDesc) { + if (!SDIsNullRemoteDecoder(aDesc)) { + return nullptr; + } + + const auto& sdrd = aDesc.get_SurfaceDescriptorGPUVideo() + .get_SurfaceDescriptorRemoteDecoder(); + RefPtr<VideoBridgeParent> parent = + VideoBridgeParent::GetSingleton(sdrd.source()); + if (!parent) { + MOZ_ASSERT_UNREACHABLE("unexpected to be called"); + gfxCriticalNote << "TexUnpackSurface failed to get VideoBridgeParent"; + return nullptr; + } + RefPtr<TextureHost> texture = + parent->LookupTexture(mContentId, sdrd.handle()); + if (!texture) { + MOZ_ASSERT_UNREACHABLE("unexpected to be called"); + gfxCriticalNote << "TexUnpackSurface failed to get TextureHost"; + return nullptr; + } + + RefPtr<gfx::DataSourceSurface> surf = texture->GetAsSurface(); + + return surf.forget(); +} + void CanvasTranslator::CheckpointReached() { CheckAndSignalWriter(); } void CanvasTranslator::PauseTranslation() { diff --git a/gfx/layers/ipc/CanvasTranslator.h b/gfx/layers/ipc/CanvasTranslator.h index 5258e0c529..e2b6c587b4 100644 --- a/gfx/layers/ipc/CanvasTranslator.h +++ b/gfx/layers/ipc/CanvasTranslator.h @@ -219,6 +219,9 @@ class CanvasTranslator final : public gfx::InlineTranslator, already_AddRefed<gfx::SourceSurface> LookupExternalSurface( uint64_t aKey) final; + already_AddRefed<gfx::SourceSurface> LookupSourceSurfaceFromSurfaceDescriptor( + const SurfaceDescriptor& aDesc) final; + /** * Gets the cached DataSourceSurface, if it exists, associated with a * SourceSurface from another process. @@ -274,6 +277,8 @@ class CanvasTranslator final : public gfx::InlineTranslator, void GetDataSurface(uint64_t aSurfaceRef); + static void Shutdown(); + private: ~CanvasTranslator(); @@ -333,6 +338,7 @@ class CanvasTranslator final : public gfx::InlineTranslator, #if defined(XP_WIN) RefPtr<ID3D11Device> mDevice; #endif + static StaticRefPtr<gfx::SharedContextWebgl> sSharedContext; RefPtr<gfx::SharedContextWebgl> mSharedContext; RefPtr<RemoteTextureOwnerClient> mRemoteTextureOwner; diff --git a/gfx/layers/ipc/CompositorBridgeChild.cpp b/gfx/layers/ipc/CompositorBridgeChild.cpp index 070e6d673e..83374e3d30 100644 --- a/gfx/layers/ipc/CompositorBridgeChild.cpp +++ b/gfx/layers/ipc/CompositorBridgeChild.cpp @@ -18,8 +18,7 @@ #include "mozilla/layers/CanvasChild.h" #include "mozilla/layers/WebRenderLayerManager.h" #include "mozilla/layers/PTextureChild.h" -#include "mozilla/layers/TextureClient.h" // for TextureClient -#include "mozilla/layers/TextureClientPool.h" // for TextureClientPool +#include "mozilla/layers/TextureClient.h" // for TextureClient #include "mozilla/layers/WebRenderBridgeChild.h" #include "mozilla/layers/SyncObject.h" // for SyncObjectClient #include "mozilla/gfx/CanvasManagerChild.h" @@ -135,10 +134,6 @@ void CompositorBridgeChild::Destroy() { // happens. RefPtr<CompositorBridgeChild> selfRef = this; - for (size_t i = 0; i < mTexturePools.Length(); i++) { - mTexturePools[i]->Destroy(); - } - if (mSectionAllocator) { delete mSectionAllocator; mSectionAllocator = nullptr; @@ -275,9 +270,6 @@ bool CompositorBridgeChild::CompositorIsInGPUProcess() { mozilla::ipc::IPCResult CompositorBridgeChild::RecvDidComposite( const LayersId& aId, const nsTArray<TransactionId>& aTransactionIds, const TimeStamp& aCompositeStart, const TimeStamp& aCompositeEnd) { - // Hold a reference to keep texture pools alive. See bug 1387799 - const auto texturePools = mTexturePools.Clone(); - for (const auto& id : aTransactionIds) { if (mLayerManager) { MOZ_ASSERT(!aId.IsValid()); @@ -293,10 +285,6 @@ mozilla::ipc::IPCResult CompositorBridgeChild::RecvDidComposite( } } - for (size_t i = 0; i < texturePools.Length(); i++) { - texturePools[i]->ReturnDeferredClients(); - } - return IPC_OK(); } diff --git a/gfx/layers/ipc/CompositorBridgeChild.h b/gfx/layers/ipc/CompositorBridgeChild.h index 7e0a4799fe..7ac7ccc197 100644 --- a/gfx/layers/ipc/CompositorBridgeChild.h +++ b/gfx/layers/ipc/CompositorBridgeChild.h @@ -45,7 +45,6 @@ class CompositorManagerChild; class CompositorOptions; class WebRenderLayerManager; class TextureClient; -class TextureClientPool; struct FrameMetrics; struct FwdTransactionCounter; @@ -233,8 +232,6 @@ class CompositorBridgeChild final : public PCompositorBridgeChild, nsCOMPtr<nsISerialEventTarget> mThread; - AutoTArray<RefPtr<TextureClientPool>, 2> mTexturePools; - uint64_t mProcessToken; FixedSizeSmallShmemSectionAllocator* mSectionAllocator; diff --git a/gfx/layers/ipc/CompositorBridgeParent.cpp b/gfx/layers/ipc/CompositorBridgeParent.cpp index 3982791357..767ea47b2f 100644 --- a/gfx/layers/ipc/CompositorBridgeParent.cpp +++ b/gfx/layers/ipc/CompositorBridgeParent.cpp @@ -1777,20 +1777,20 @@ int32_t RecordContentFrameTime( ContentFrameMarker{}); } - mozilla::glean::gfx_content_frame_time::from_paint.AccumulateSamples( - {static_cast<unsigned long long>(fracLatencyNorm)}); + mozilla::glean::gfx_content_frame_time::from_paint.AccumulateSingleSample( + static_cast<unsigned long long>(fracLatencyNorm)); if (!(aTxnId == VsyncId()) && aVsyncStart) { latencyMs = (aCompositeEnd - aVsyncStart).ToMilliseconds(); latencyNorm = latencyMs / aVsyncRate.ToMilliseconds(); fracLatencyNorm = lround(latencyNorm * 100.0); int32_t result = fracLatencyNorm; - mozilla::glean::gfx_content_frame_time::from_vsync.AccumulateSamples( - {static_cast<unsigned long long>(fracLatencyNorm)}); + mozilla::glean::gfx_content_frame_time::from_vsync.AccumulateSingleSample( + static_cast<unsigned long long>(fracLatencyNorm)); if (aContainsSVGGroup) { - mozilla::glean::gfx_content_frame_time::with_svg.AccumulateSamples( - {static_cast<unsigned long long>(fracLatencyNorm)}); + mozilla::glean::gfx_content_frame_time::with_svg.AccumulateSingleSample( + static_cast<unsigned long long>(fracLatencyNorm)); } // Record CONTENT_FRAME_TIME_REASON. @@ -1889,8 +1889,8 @@ int32_t RecordContentFrameTime( fracLatencyNorm = lround(latencyNorm * 100.0); } mozilla::glean::gfx_content_frame_time::without_resource_upload - .AccumulateSamples( - {static_cast<unsigned long long>(fracLatencyNorm)}); + .AccumulateSingleSample( + static_cast<unsigned long long>(fracLatencyNorm)); if (aStats) { latencyMs -= (double(aStats->gpu_cache_upload_time) / 1000000.0); @@ -1898,8 +1898,8 @@ int32_t RecordContentFrameTime( fracLatencyNorm = lround(latencyNorm * 100.0); } mozilla::glean::gfx_content_frame_time::without_resource_upload - .AccumulateSamples( - {static_cast<unsigned long long>(fracLatencyNorm)}); + .AccumulateSingleSample( + static_cast<unsigned long long>(fracLatencyNorm)); } return result; } diff --git a/gfx/layers/ipc/LayersMessageUtils.h b/gfx/layers/ipc/LayersMessageUtils.h index a4e9557ac3..a0da6154d5 100644 --- a/gfx/layers/ipc/LayersMessageUtils.h +++ b/gfx/layers/ipc/LayersMessageUtils.h @@ -18,6 +18,7 @@ #include "ipc/IPCMessageUtils.h" #include "mozilla/ScrollSnapInfo.h" #include "mozilla/ServoBindings.h" +#include "mozilla/dom/WebGLIpdl.h" #include "mozilla/ipc/ByteBuf.h" #include "mozilla/ipc/ProtocolMessageUtils.h" #include "mozilla/layers/APZInputBridge.h" @@ -48,15 +49,11 @@ namespace IPC { template <> struct ParamTraits<mozilla::layers::LayersId> - : public PlainOldDataSerializer<mozilla::layers::LayersId> {}; + : public ParamTraits_TiedFields<mozilla::layers::LayersId> {}; template <typename T> struct ParamTraits<mozilla::layers::BaseTransactionId<T>> - : public PlainOldDataSerializer<mozilla::layers::BaseTransactionId<T>> {}; - -template <> -struct ParamTraits<mozilla::VsyncId> - : public PlainOldDataSerializer<mozilla::VsyncId> {}; + : public ParamTraits_TiedFields<mozilla::layers::BaseTransactionId<T>> {}; template <> struct ParamTraits<mozilla::VsyncEvent> { @@ -419,7 +416,7 @@ struct ParamTraits<mozilla::StyleScrollSnapStop> template <> struct ParamTraits<mozilla::ScrollSnapTargetId> - : public PlainOldDataSerializer<mozilla::ScrollSnapTargetId> {}; + : public ParamTraits_IsEnumCase<mozilla::ScrollSnapTargetId> {}; template <> struct ParamTraits<mozilla::SnapPoint> { @@ -495,26 +492,12 @@ struct ParamTraits<mozilla::ScrollSnapInfo> { }; template <> -struct ParamTraits<mozilla::layers::OverscrollBehaviorInfo> { - // Not using PlainOldDataSerializer so we get enum validation - // for the members. - - typedef mozilla::layers::OverscrollBehaviorInfo paramType; - - static void Write(MessageWriter* aWriter, const paramType& aParam) { - WriteParam(aWriter, aParam.mBehaviorX); - WriteParam(aWriter, aParam.mBehaviorY); - } - - static bool Read(MessageReader* aReader, paramType* aResult) { - return (ReadParam(aReader, &aResult->mBehaviorX) && - ReadParam(aReader, &aResult->mBehaviorY)); - } -}; +struct ParamTraits<mozilla::layers::OverscrollBehaviorInfo> + : public ParamTraits_TiedFields<mozilla::layers::OverscrollBehaviorInfo> {}; template <typename T> struct ParamTraits<mozilla::ScrollGeneration<T>> - : PlainOldDataSerializer<mozilla::ScrollGeneration<T>> {}; + : public ParamTraits_TiedFields<mozilla::ScrollGeneration<T>> {}; template <> struct ParamTraits<mozilla::ScrollUpdateType> diff --git a/gfx/layers/ipc/SharedSurfacesMemoryReport.h b/gfx/layers/ipc/SharedSurfacesMemoryReport.h index 81baf1349f..31e27bccad 100644 --- a/gfx/layers/ipc/SharedSurfacesMemoryReport.h +++ b/gfx/layers/ipc/SharedSurfacesMemoryReport.h @@ -12,6 +12,7 @@ #include "base/process.h" #include "ipc/IPCMessageUtils.h" #include "ipc/IPCMessageUtilsSpecializations.h" +#include "mozilla/dom/WebGLIpdl.h" #include "mozilla/gfx/Point.h" // for IntSize namespace mozilla { @@ -26,8 +27,16 @@ class SharedSurfacesMemoryReport final { int32_t mStride; uint32_t mConsumers; bool mCreatorRef; + PaddingField<bool, 3> _padding; + + auto MutTiedFields() { + return std::tie(mCreatorPid, mSize, mStride, mConsumers, mCreatorRef, + _padding); + } }; + auto MutTiedFields() { return std::tie(mSurfaces); } + std::unordered_map<uint64_t, SurfaceEntry> mSurfaces; }; @@ -37,21 +46,13 @@ class SharedSurfacesMemoryReport final { namespace IPC { template <> -struct ParamTraits<mozilla::layers::SharedSurfacesMemoryReport> { - typedef mozilla::layers::SharedSurfacesMemoryReport paramType; - - static void Write(MessageWriter* aWriter, const paramType& aParam) { - WriteParam(aWriter, aParam.mSurfaces); - } - - static bool Read(MessageReader* aReader, paramType* aResult) { - return ReadParam(aReader, &aResult->mSurfaces); - } -}; +struct ParamTraits<mozilla::layers::SharedSurfacesMemoryReport> + : public ParamTraits_TiedFields< + mozilla::layers::SharedSurfacesMemoryReport> {}; template <> struct ParamTraits<mozilla::layers::SharedSurfacesMemoryReport::SurfaceEntry> - : public PlainOldDataSerializer< + : public ParamTraits_TiedFields< mozilla::layers::SharedSurfacesMemoryReport::SurfaceEntry> {}; } // namespace IPC diff --git a/gfx/layers/moz.build b/gfx/layers/moz.build index 384611b68b..176e6b54a4 100644 --- a/gfx/layers/moz.build +++ b/gfx/layers/moz.build @@ -125,7 +125,6 @@ EXPORTS.mozilla.layers += [ "client/GPUVideoTextureClient.h", "client/ImageClient.h", "client/TextureClient.h", - "client/TextureClientPool.h", "client/TextureClientRecycleAllocator.h", "client/TextureClientSharedSurface.h", "client/TextureRecorded.h", @@ -354,7 +353,6 @@ UNIFIED_SOURCES += [ "client/CompositableClient.cpp", "client/GPUVideoTextureClient.cpp", "client/ImageClient.cpp", - "client/TextureClientPool.cpp", "client/TextureClientRecycleAllocator.cpp", "client/TextureClientSharedSurface.cpp", "client/TextureRecorded.cpp", diff --git a/gfx/layers/wr/StackingContextHelper.cpp b/gfx/layers/wr/StackingContextHelper.cpp index d5ffbf26a0..2c0627f63f 100644 --- a/gfx/layers/wr/StackingContextHelper.cpp +++ b/gfx/layers/wr/StackingContextHelper.cpp @@ -272,5 +272,14 @@ Maybe<gfx::Matrix4x4> StackingContextHelper::GetDeferredTransformMatrix() } } +void StackingContextHelper::ClearDeferredTransformItem() const { + mDeferredTransformItem = nullptr; +} + +void StackingContextHelper::RestoreDeferredTransformItem( + nsDisplayTransform* aItem) const { + mDeferredTransformItem = aItem; +} + } // namespace layers } // namespace mozilla diff --git a/gfx/layers/wr/StackingContextHelper.h b/gfx/layers/wr/StackingContextHelper.h index 368449a2fd..8239b2db0d 100644 --- a/gfx/layers/wr/StackingContextHelper.h +++ b/gfx/layers/wr/StackingContextHelper.h @@ -56,6 +56,12 @@ class MOZ_RAII StackingContextHelper { nsDisplayTransform* GetDeferredTransformItem() const; Maybe<gfx::Matrix4x4> GetDeferredTransformMatrix() const; + // Functions for temporarily clearing and restoring the deferred + // transform item during WebRender display list building. These are + // used to ensure deferred transforms are not applied in duplicate + // to nested nodes in the WebRenderScrollData tree. + void ClearDeferredTransformItem() const; + void RestoreDeferredTransformItem(nsDisplayTransform* aItem) const; bool AffectsClipPositioning() const { return mAffectsClipPositioning; } Maybe<wr::WrSpatialId> ReferenceFrameId() const { return mReferenceFrameId; } @@ -105,19 +111,19 @@ class MOZ_RAII StackingContextHelper { // item (i.e. the closest ancestor nsDisplayTransform item of the item that // created this StackingContextHelper). And then we use // mDeferredAncestorTransform to store the product of all the other transforms - // that were deferred. As a result, there is an invariant here that if - // mDeferredTransformItem is nullptr, mDeferredAncestorTransform will also - // be Nothing(). Note that we can only do this if the nsDisplayTransform items - // share the same ASR. If we are processing an nsDisplayTransform item with a - // different ASR than the previously-deferred item, we assume that the - // previously-deferred transform will get sent to APZ as part of a separate - // WebRenderLayerScrollData item, and so we don't need to bother with any - // merging. (The merging probably wouldn't even make sense because the - // coordinate spaces might be different in the face of async scrolling). This - // behaviour of forcing a WebRenderLayerScrollData item to be generated when - // the ASR changes is implemented in + // that were deferred. Note that this means we only need to look at + // mDeferredAncestorTransform if mDeferredTransformItem is set. Note that we + // can only do this if the nsDisplayTransform items share the same ASR. If we + // are processing an nsDisplayTransform item with a different ASR than the + // previously-deferred item, we assume that the previously-deferred transform + // will get sent to APZ as part of a separate WebRenderLayerScrollData item, + // and so we don't need to bother with any merging. (The merging probably + // wouldn't even make sense because the coordinate spaces might be different + // in the face of async scrolling). This behaviour of forcing a + // WebRenderLayerScrollData item to be generated when the ASR changes is + // implemented in // WebRenderCommandBuilder::CreateWebRenderCommandsFromDisplayList. - nsDisplayTransform* mDeferredTransformItem; + mutable nsDisplayTransform* mDeferredTransformItem; Maybe<gfx::Matrix4x4> mDeferredAncestorTransform; bool mRasterizeLocally; diff --git a/gfx/layers/wr/WebRenderBridgeParent.cpp b/gfx/layers/wr/WebRenderBridgeParent.cpp index 83139b6af6..68e0c7af8c 100644 --- a/gfx/layers/wr/WebRenderBridgeParent.cpp +++ b/gfx/layers/wr/WebRenderBridgeParent.cpp @@ -1919,6 +1919,10 @@ mozilla::ipc::IPCResult WebRenderBridgeParent::RecvClearCachedResources() { wr::AsUint64(mPipelineId), wr::AsUint64(mApi->GetId()), IsRootWebRenderBridgeParent()); + if (!IsRootWebRenderBridgeParent()) { + mApi->FlushPendingWrTransactionEventsWithoutWait(); + } + // Clear resources wr::TransactionBuilder txn(mApi); txn.SetLowPriority(true); @@ -2618,17 +2622,17 @@ void WebRenderBridgeParent::ScheduleGenerateFrame(wr::RenderReasons aReasons) { } void WebRenderBridgeParent::FlushRendering(wr::RenderReasons aReasons, - bool aWaitForPresent) { + bool aBlocking) { if (mDestroyed) { return; } - // This gets called during e.g. window resizes, so we need to flush the - // scene (which has the display list at the new window size). - FlushSceneBuilds(); - FlushFrameGeneration(aReasons); - if (aWaitForPresent) { + if (aBlocking) { + FlushSceneBuilds(); + FlushFrameGeneration(aReasons); FlushFramePresentation(); + } else { + ScheduleGenerateFrame(aReasons); } } diff --git a/gfx/layers/wr/WebRenderBridgeParent.h b/gfx/layers/wr/WebRenderBridgeParent.h index d8d80d1047..2c23779fb2 100644 --- a/gfx/layers/wr/WebRenderBridgeParent.h +++ b/gfx/layers/wr/WebRenderBridgeParent.h @@ -244,7 +244,7 @@ class WebRenderBridgeParent final : public PWebRenderBridgeParent, return aFontKey.mNamespace == mIdNamespace; } - void FlushRendering(wr::RenderReasons aReasons, bool aWaitForPresent = true); + void FlushRendering(wr::RenderReasons aReasons, bool aBlocking = true); /** * Schedule generating WebRender frame definitely at next composite timing. diff --git a/gfx/layers/wr/WebRenderCommandBuilder.cpp b/gfx/layers/wr/WebRenderCommandBuilder.cpp index e1bb2e1127..d7c7468f56 100644 --- a/gfx/layers/wr/WebRenderCommandBuilder.cpp +++ b/gfx/layers/wr/WebRenderCommandBuilder.cpp @@ -1882,9 +1882,8 @@ struct NewLayerData { ScrollableLayerGuid::ViewID mDeferredId = ScrollableLayerGuid::NULL_SCROLL_ID; bool mTransformShouldGetOwnLayer = false; - void ComputeDeferredTransformInfo( - const StackingContextHelper& aSc, nsDisplayItem* aItem, - nsDisplayTransform* aLastDeferredTransform) { + void ComputeDeferredTransformInfo(const StackingContextHelper& aSc, + nsDisplayItem* aItem) { // See the comments on StackingContextHelper::mDeferredTransformItem // for an overview of what deferred transforms are. // In the case where we deferred a transform, but have a child display @@ -1900,14 +1899,6 @@ struct NewLayerData { // that we deferred, and a child WebRenderLayerScrollData item that // holds the scroll metadata for the child's ASR. mDeferredItem = aSc.GetDeferredTransformItem(); - // If this deferred transform is already slated to be emitted onto an - // ancestor layer, do not emit it on this layer as well. Note that it's - // sufficient to check the most recently deferred item here, because - // there's only one per stacking context, and we emit it when changing - // stacking contexts. - if (mDeferredItem == aLastDeferredTransform) { - mDeferredItem = nullptr; - } if (mDeferredItem) { // It's possible the transform's ASR is not only an ancestor of // the item's ASR, but an ancestor of stopAtAsr. In such cases, @@ -2071,10 +2062,7 @@ void WebRenderCommandBuilder::CreateWebRenderCommandsFromDisplayList( newLayerData->mLayerCountBeforeRecursing = mLayerScrollData.size(); newLayerData->mStopAtAsr = mAsrStack.empty() ? nullptr : mAsrStack.back(); - newLayerData->ComputeDeferredTransformInfo( - aSc, item, - mDeferredTransformStack.empty() ? nullptr - : mDeferredTransformStack.back()); + newLayerData->ComputeDeferredTransformInfo(aSc, item); // Ensure our children's |stopAtAsr| is not be an ancestor of our // |stopAtAsr|, otherwise we could get cyclic scroll metadata @@ -2096,10 +2084,12 @@ void WebRenderCommandBuilder::CreateWebRenderCommandsFromDisplayList( mAsrStack.push_back(stopAtAsrForChildren); // If we're going to emit a deferred transform onto this layer, - // keep track of that so descendant layers know not to emit the - // same deferred transform. + // clear the deferred transform from the StackingContextHelper + // while we are building the subtree of descendant layers. + // This ensures that the deferred transform is not applied in + // duplicate to any of our descendant layers. if (newLayerData->mDeferredItem) { - mDeferredTransformStack.push_back(newLayerData->mDeferredItem); + aSc.ClearDeferredTransformItem(); } } } @@ -2143,8 +2133,7 @@ void WebRenderCommandBuilder::CreateWebRenderCommandsFromDisplayList( mAsrStack.pop_back(); if (newLayerData->mDeferredItem) { - MOZ_ASSERT(!mDeferredTransformStack.empty()); - mDeferredTransformStack.pop_back(); + aSc.RestoreDeferredTransformItem(newLayerData->mDeferredItem); } const ActiveScrolledRoot* stopAtAsr = newLayerData->mStopAtAsr; diff --git a/gfx/layers/wr/WebRenderCommandBuilder.h b/gfx/layers/wr/WebRenderCommandBuilder.h index 68c9a4ce63..8f17a73e3f 100644 --- a/gfx/layers/wr/WebRenderCommandBuilder.h +++ b/gfx/layers/wr/WebRenderCommandBuilder.h @@ -208,9 +208,6 @@ class WebRenderCommandBuilder final { // need this so that WebRenderLayerScrollData items that deeper in the // tree don't duplicate scroll metadata that their ancestors already have. std::vector<const ActiveScrolledRoot*> mAsrStack; - // A similar stack to track the deferred transform that we decided to emit - // most recently. - std::vector<nsDisplayTransform*> mDeferredTransformStack; const ActiveScrolledRoot* mLastAsr; WebRenderUserDataRefTable mWebRenderUserDatas; diff --git a/gfx/layers/wr/WebRenderLayerManager.cpp b/gfx/layers/wr/WebRenderLayerManager.cpp index 66b98a7db3..c29517c696 100644 --- a/gfx/layers/wr/WebRenderLayerManager.cpp +++ b/gfx/layers/wr/WebRenderLayerManager.cpp @@ -96,6 +96,7 @@ bool WebRenderLayerManager::Initialize( } mWrChild = static_cast<WebRenderBridgeChild*>(bridge); + mHasFlushedThisChild = false; TextureFactoryIdentifier textureFactoryIdentifier; wr::MaybeIdNamespace idNamespace; @@ -694,24 +695,30 @@ void WebRenderLayerManager::FlushRendering(wr::RenderReasons aReasons) { } MOZ_ASSERT(mWidget); - // If value of IsResizingNativeWidget() is nothing, we assume that resizing - // might happen. - bool resizing = mWidget && mWidget->IsResizingNativeWidget().valueOr(true); + // If widget bounds size is different from the last flush, consider + // this to be a resize. It's important to use GetClientSize here, + // because that has extra plumbing to support initial display cases + // where the widget doesn't yet have real bounds. + LayoutDeviceIntSize widgetSize = mWidget->GetClientSize(); + bool resizing = widgetSize != mFlushWidgetSize; + mFlushWidgetSize = widgetSize; if (resizing) { aReasons = aReasons | wr::RenderReasons::RESIZE; } - // Limit async FlushRendering to !resizing and Win DComp. - // XXX relax the limitation - if (WrBridge()->GetCompositorUseDComp() && !resizing) { - cBridge->SendFlushRenderingAsync(aReasons); - } else if (mWidget->SynchronouslyRepaintOnResize() || - StaticPrefs::layers_force_synchronous_resize()) { + // Check for the conditions where we we force a sync flush. The first + // flush for this child should always be sync. Resizes should be + // sometimes be sync. Everything else can be async. + if (!mHasFlushedThisChild || + (resizing && (mWidget->SynchronouslyRepaintOnResize() || + StaticPrefs::layers_force_synchronous_resize()))) { cBridge->SendFlushRendering(aReasons); } else { cBridge->SendFlushRenderingAsync(aReasons); } + + mHasFlushedThisChild = true; } void WebRenderLayerManager::WaitOnTransactionProcessed() { diff --git a/gfx/layers/wr/WebRenderLayerManager.h b/gfx/layers/wr/WebRenderLayerManager.h index 31fc9b6678..5ee2cc76a5 100644 --- a/gfx/layers/wr/WebRenderLayerManager.h +++ b/gfx/layers/wr/WebRenderLayerManager.h @@ -226,6 +226,7 @@ class WebRenderLayerManager final : public WindowRenderer { nsIWidget* MOZ_NON_OWNING_REF mWidget; RefPtr<WebRenderBridgeChild> mWrChild; + bool mHasFlushedThisChild; RefPtr<TransactionIdAllocator> mTransactionIdAllocator; TransactionId mLatestTransactionId; @@ -273,6 +274,8 @@ class WebRenderLayerManager final : public WindowRenderer { UniquePtr<wr::DisplayListBuilder> mDLBuilder; ScrollUpdatesMap mPendingScrollUpdates; + + LayoutDeviceIntSize mFlushWidgetSize; }; } // namespace layers |