From 7d4a5ffc8c12eecdc14a91f2e60c0fded54cf8f8 Mon Sep 17 00:00:00 2001 From: roytam1 Date: Wed, 28 Jun 2023 15:52:17 +0800 Subject: [PATCH] import changes from `dev' branch of rmottola/Arctic-Fox: - Bug 1190469 - Refactor some code to have a general-purpose snap function available in APZC. r=botond (a4e8706df5) - Bug 1225761 - Clear axis lock in CancelAnimation and EndTouch. r=botond (1436279d0d) - Bug 1226316. Fixup to only expand displayport when skating with apz. r=kats (51c49a8fca) - Bug 1181703 - Allow re-entering the panning state if a second finger is tapped while panning with one finger. r=botond (e87cd85058) - Bug 1181703 - Add a gtest for making sure a pan can be continued after a second finger does a tap. r=botond (16354bec40) - Bug 1190469 - Request scroll snapping in a few places that animations are cancelled so that we don't leave things unsnapped. r=botond (4b0ba5e513) - Bug 960317 - Remove cross-slide code from APZC. r=botond (b2a045dbc9) - Bug 1231972 - Document how the apz.fling_friction pref is used. r=kats. DONTBUILD for comment change (c181588265) - Bug 1221593 - Don't reset other axis' scroll position during APZ drag. r=kats (9de4e134c6) - Bug 1219929 - Fix bad MouseInputData case. r=me (3630a3fcfa) - Bug 1211506 - Respect the APZ lock ordering in AsyncPanZoomController::OnScrollWheel(). r=mstange (3f152ffabf) - Bug 1229125 - Reset velocity in OnPanEnd if there's nowhere to scroll. r=botond (66ec61692a) - fix misspatch (f7aea52b9d) - Bug 1221186 - Don't clobber a fling-snap with a snap-to-where-we-are-now in the overscroll handoff chain. r=botond (7d4862bbe6) - Bug 1204932 - When C++APZ is enabled, elements that are zoomed with double tap should be centered. r=botond (b9e02a24b4) - Bug 1141884 - Rename ResetInputState and make it only apply to touch events. r=botond (aab239f51a) - Bug 1229125 - Correct velocity computation for pan gesture events. r=botond (b1f11b2e04) - Bug 1228559 - get MSG by audio channel type. r=roc (0e5e32f9bb) - Bug 1191207 - cancel chrome checking (74bde1263e) - Bug 1155469 - Mark nsTextEditorState::mTextCtrlElement as MOZ_NON_OWNING_REF; r=baku (7e6f4fa9d0) - Bug 1218072 - crash in nsTextEditorState::FinishedRestoringSelection, r=smaug (c7811e18ea) - bits of 1190258 (7f8fbbddfa) - Bug 1130237: [MSE] P1. Only ever return a frame if we have data. r=gerald (1ed595a5f8) - Bug 1130237: P2. Reset decoder state even if no decoder has been created yet. r=gerald (d352c0314b) - Bug 1130237: [MSE] P3. Add mochitest testing behavior. r=gerald (ce2b710a7e) - Bug 1229987: [MSE] P1. Ensure next random access point properly calculated after seek. r=gerald (d9a435f0b5) --- dom/html/HTMLMediaElement.cpp | 4 +- dom/html/nsTextEditorState.cpp | 5 +- dom/html/nsTextEditorState.h | 5 +- dom/media/MediaData.cpp | 11 +- dom/media/MediaFormatReader.cpp | 1 + dom/media/mediasource/MediaSourceDemuxer.cpp | 48 +++-- dom/media/mediasource/MediaSourceDemuxer.h | 4 + dom/media/mediasource/test/mochitest.ini | 2 + .../test/test_LoadedDataFired_mp4.html | 68 +++++++ gfx/layers/apz/src/APZUtils.h | 12 +- gfx/layers/apz/src/AsyncPanZoomController.cpp | 170 +++++++++++------- gfx/layers/apz/src/AsyncPanZoomController.h | 16 +- gfx/layers/apz/src/Axis.cpp | 3 +- gfx/layers/apz/src/InputBlockState.cpp | 6 + gfx/layers/apz/src/InputBlockState.h | 5 + gfx/layers/apz/src/InputQueue.cpp | 6 +- .../test/gtest/TestAsyncPanZoomController.cpp | 75 ++++++++ gfx/thebes/gfxPrefs.h | 1 - modules/libpref/init/all.js | 1 - 19 files changed, 340 insertions(+), 103 deletions(-) create mode 100644 dom/media/mediasource/test/test_LoadedDataFired_mp4.html diff --git a/dom/html/HTMLMediaElement.cpp b/dom/html/HTMLMediaElement.cpp index 83c68861bb..79912107b5 100644 --- a/dom/html/HTMLMediaElement.cpp +++ b/dom/html/HTMLMediaElement.cpp @@ -4824,7 +4824,7 @@ HTMLMediaElement::NotifyAudioChannelAgent(bool aPlaying) NS_IMETHODIMP HTMLMediaElement::WindowVolumeChanged(float aVolume, bool aMuted) { - NS_ENSURE_TRUE(nsContentUtils::IsCallerChrome(), NS_ERROR_NOT_AVAILABLE); + MOZ_ASSERT(NS_IsMainThread()); UpdateChannelMuteState(aVolume, aMuted); @@ -4993,7 +4993,7 @@ NS_IMETHODIMP HTMLMediaElement::WindowAudioCaptureChanged() uint64_t id = window->WindowID(); MediaStreamGraph* msg = MediaStreamGraph::GetInstance(MediaStreamGraph::AUDIO_THREAD_DRIVER, - AudioChannel::Normal); + mAudioChannel); if (GetSrcMediaStream()) { mCaptureStreamPort = msg->ConnectToCaptureStream(id, GetSrcMediaStream()); diff --git a/dom/html/nsTextEditorState.cpp b/dom/html/nsTextEditorState.cpp index b4ab7e1647..8f472a9d6e 100644 --- a/dom/html/nsTextEditorState.cpp +++ b/dom/html/nsTextEditorState.cpp @@ -110,7 +110,10 @@ public: } mTextEditorState->mSelectionRestoreEagerInit = false; } - mTextEditorState->FinishedRestoringSelection(); + + if (mTextEditorState) { + mTextEditorState->FinishedRestoringSelection(); + } return NS_OK; } diff --git a/dom/html/nsTextEditorState.h b/dom/html/nsTextEditorState.h index a64caee183..f2e5e83b45 100644 --- a/dom/html/nsTextEditorState.h +++ b/dom/html/nsTextEditorState.h @@ -13,6 +13,7 @@ #include "nsITextControlFrame.h" #include "nsCycleCollectionParticipant.h" #include "mozilla/dom/Element.h" +#include "mozilla/Attributes.h" #include "mozilla/WeakPtr.h" class nsTextInputListener; @@ -282,7 +283,9 @@ private: friend class InitializationGuard; friend class PrepareEditorEvent; - nsITextControlElement* const mTextCtrlElement; + // The text control element owns this object, and ensures that this object + // has a smaller lifetime. + nsITextControlElement* const MOZ_NON_OWNING_REF mTextCtrlElement; RefPtr mSelCon; RefPtr mRestoringSelection; nsCOMPtr mEditor; diff --git a/dom/media/MediaData.cpp b/dom/media/MediaData.cpp index dbbe172020..80ebc3a4b1 100644 --- a/dom/media/MediaData.cpp +++ b/dom/media/MediaData.cpp @@ -476,9 +476,9 @@ VideoData::Create(const VideoInfo& aInfo, MediaRawData::MediaRawData() : MediaData(RAW_DATA, 0) + , mCrypto(mCryptoInternal) , mData(nullptr) , mSize(0) - , mCrypto(mCryptoInternal) , mBuffer(nullptr) , mCapacity(0) { @@ -486,15 +486,17 @@ MediaRawData::MediaRawData() MediaRawData::MediaRawData(const uint8_t* aData, size_t aSize) : MediaData(RAW_DATA, 0) + , mCrypto(mCryptoInternal) , mData(nullptr) , mSize(0) - , mCrypto(mCryptoInternal) , mBuffer(nullptr) , mCapacity(0) { if (!EnsureCapacity(aSize)) { return; } + + // We ensure sufficient capacity above so this shouldn't fail. memcpy(mData, aData, aSize); mSize = aSize; } @@ -515,6 +517,7 @@ MediaRawData::Clone() const if (!s->EnsureCapacity(mSize)) { return nullptr; } + memcpy(s->mData, mData, mSize); s->mSize = mSize; } @@ -535,6 +538,7 @@ MediaRawData::EnsureCapacity(size_t aSize) if (!newBuffer) { return false; } + // Find alignment address. const uintptr_t alignmask = RAW_DATA_ALIGNMENT; uint8_t* newData = reinterpret_cast( @@ -557,7 +561,6 @@ size_t MediaRawData::SizeOfIncludingThis(MallocSizeOf aMallocSizeOf) const { size_t size = aMallocSizeOf(this); - size += aMallocSizeOf(mBuffer.get()); return size; } @@ -592,6 +595,7 @@ MediaRawDataWriter::SetSize(size_t aSize) if (aSize > mTarget->mSize && !EnsureSize(aSize)) { return false; } + mTarget->mSize = aSize; return true; } @@ -619,6 +623,7 @@ MediaRawDataWriter::Replace(const uint8_t* aData, size_t aSize) if (!EnsureSize(aSize)) { return false; } + memcpy(mTarget->mData, aData, aSize); mTarget->mSize = aSize; return true; diff --git a/dom/media/MediaFormatReader.cpp b/dom/media/MediaFormatReader.cpp index b29a3de4e5..085686d37b 100644 --- a/dom/media/MediaFormatReader.cpp +++ b/dom/media/MediaFormatReader.cpp @@ -1244,6 +1244,7 @@ MediaFormatReader::Flush(TrackType aTrack) auto& decoder = GetDecoderData(aTrack); if (!decoder.mDecoder) { + decoder.ResetState(); return; } diff --git a/dom/media/mediasource/MediaSourceDemuxer.cpp b/dom/media/mediasource/MediaSourceDemuxer.cpp index 4de3922b40..8833819eb1 100644 --- a/dom/media/mediasource/MediaSourceDemuxer.cpp +++ b/dom/media/mediasource/MediaSourceDemuxer.cpp @@ -299,6 +299,7 @@ MediaSourceTrackDemuxer::MediaSourceTrackDemuxer(MediaSourceDemuxer* aParent, , mManager(aManager) , mType(aType) , mMonitor("MediaSourceTrackDemuxer") + , mReset(true) { } @@ -331,6 +332,8 @@ MediaSourceTrackDemuxer::Reset() RefPtr self = this; nsCOMPtr task = NS_NewRunnableFunction([self] () { + self->mNextSample.reset(); + self->mReset = true; self->mManager->Seek(self->mType, TimeUnit(), TimeUnit()); { MonitorAutoLock mon(self->mMonitor); @@ -388,6 +391,14 @@ MediaSourceTrackDemuxer::DoSeek(media::TimeUnit aTime) } TimeUnit seekTime = mManager->Seek(mType, aTime, MediaSourceDemuxer::EOS_FUZZ); + bool error; + RefPtr sample = + mManager->GetSample(mType, + media::TimeUnit(), + error); + MOZ_ASSERT(!error && sample); + mNextSample = Some(sample); + mReset = false; { MonitorAutoLock mon(mMonitor); mNextRandomAccessPoint = mManager->GetNextRandomAccessPoint(mType); @@ -398,19 +409,34 @@ MediaSourceTrackDemuxer::DoSeek(media::TimeUnit aTime) RefPtr MediaSourceTrackDemuxer::DoGetSamples(int32_t aNumSamples) { - bool error; - RefPtr sample = - mManager->GetSample(mType, - MediaSourceDemuxer::EOS_FUZZ, - error); + if (mReset) { + // If a seek (or reset) was recently performed, we ensure that the data + // we are about to retrieve is still available. + TimeIntervals buffered = mManager->Buffered(mType); + buffered.SetFuzz(MediaSourceDemuxer::EOS_FUZZ); - if (!sample) { - if (error) { - return SamplesPromise::CreateAndReject(DemuxerFailureReason::DEMUXER_ERROR, __func__); + if (!buffered.Contains(TimeUnit::FromMicroseconds(0))) { + return SamplesPromise::CreateAndReject( + mManager->IsEnded() ? DemuxerFailureReason::END_OF_STREAM : + DemuxerFailureReason::WAITING_FOR_DATA, __func__); + } + mReset = false; + } + bool error = false; + RefPtr sample; + if (mNextSample) { + sample = mNextSample.ref(); + mNextSample.reset(); + } else { + sample = mManager->GetSample(mType, MediaSourceDemuxer::EOS_FUZZ, error); + if (!sample) { + if (error) { + return SamplesPromise::CreateAndReject(DemuxerFailureReason::DEMUXER_ERROR, __func__); + } + return SamplesPromise::CreateAndReject( + mManager->IsEnded() ? DemuxerFailureReason::END_OF_STREAM : + DemuxerFailureReason::WAITING_FOR_DATA, __func__); } - return SamplesPromise::CreateAndReject( - mManager->IsEnded() ? DemuxerFailureReason::END_OF_STREAM : - DemuxerFailureReason::WAITING_FOR_DATA, __func__); } RefPtr samples = new SamplesHolder; samples->mSamples.AppendElement(sample); diff --git a/dom/media/mediasource/MediaSourceDemuxer.h b/dom/media/mediasource/MediaSourceDemuxer.h index 2fe28378b3..033d8af32d 100644 --- a/dom/media/mediasource/MediaSourceDemuxer.h +++ b/dom/media/mediasource/MediaSourceDemuxer.h @@ -130,6 +130,10 @@ private: // Monitor protecting members below accessed from multiple threads. Monitor mMonitor; media::TimeUnit mNextRandomAccessPoint; + Maybe> mNextSample; + // Set to true following a reset. Ensure that the next sample demuxed + // is available at position 0. + bool mReset; }; } // namespace mozilla diff --git a/dom/media/mediasource/test/mochitest.ini b/dom/media/mediasource/test/mochitest.ini index adf77558c4..9fed5e0047 100644 --- a/dom/media/mediasource/test/mochitest.ini +++ b/dom/media/mediasource/test/mochitest.ini @@ -42,6 +42,8 @@ skip-if = true # bug 1182946 skip-if = true # bug 1182946 [test_HaveMetadataUnbufferedSeek_mp4.html] skip-if = ((os == "win" && os_version == "5.1") || (os != "win" && os != "mac")) # Only supported on osx and vista+ +[test_LoadedDataFired_mp4.html] +skip-if = ((os == "win" && os_version == "5.1") || (os != "win" && os != "mac")) # Only supported on osx and vista+ [test_LiveSeekable.html] [test_LoadedMetadataFired.html] skip-if = true # bug 1182946 diff --git a/dom/media/mediasource/test/test_LoadedDataFired_mp4.html b/dom/media/mediasource/test/test_LoadedDataFired_mp4.html new file mode 100644 index 0000000000..5af5d51a90 --- /dev/null +++ b/dom/media/mediasource/test/test_LoadedDataFired_mp4.html @@ -0,0 +1,68 @@ + + + + MSE: Check that playback only starts once we have data at time = 0 + + + + + +
+
+
+ + diff --git a/gfx/layers/apz/src/APZUtils.h b/gfx/layers/apz/src/APZUtils.h index ad8d157371..a85fddc03e 100644 --- a/gfx/layers/apz/src/APZUtils.h +++ b/gfx/layers/apz/src/APZUtils.h @@ -21,10 +21,18 @@ enum HitTestResult { }; enum CancelAnimationFlags : uint32_t { - Default = 0, /* Cancel all animations */ - ExcludeOverscroll = 1 /* Don't clear overscroll */ + Default = 0x0, /* Cancel all animations */ + ExcludeOverscroll = 0x1, /* Don't clear overscroll */ + RequestSnap = 0x2 /* Request snapping to snap points */ }; +inline CancelAnimationFlags +operator|(CancelAnimationFlags a, CancelAnimationFlags b) +{ + return static_cast(static_cast(a) + | static_cast(b)); +} + enum class ScrollSource { // scrollTo() or something similar. DOM, diff --git a/gfx/layers/apz/src/AsyncPanZoomController.cpp b/gfx/layers/apz/src/AsyncPanZoomController.cpp index ad7f9f95ca..a4424c13e4 100644 --- a/gfx/layers/apz/src/AsyncPanZoomController.cpp +++ b/gfx/layers/apz/src/AsyncPanZoomController.cpp @@ -139,9 +139,6 @@ using mozilla::gfx::PointTyped; * events we dispatched to it.\n * Units: milliseconds * - * \li\b apz.cross_slide_enabled - * Pref that enables integration with the Metro "cross-slide" gesture. - * * \li\b apz.danger_zone_x * \li\b apz.danger_zone_y * When drawing high-res tiles, we drop down to drawing low-res tiles @@ -201,8 +198,11 @@ using mozilla::gfx::PointTyped; * http://mxr.mozilla.org/mozilla-central/source/layout/style/nsStyleStruct.cpp?rev=21282be9ad95#2462 * * \li\b apz.fling_friction - * Amount of friction applied during flings. - * + * Amount of friction applied during flings. This is used in the following + * formula: v(t1) = v(t0) * (1 - f)^(t1 - t0), where v(t1) is the velocity + * for a new sample, v(t0) is the velocity at the previous sample, f is the + * value of this pref, and (t1 - t0) is the amount of time, in milliseconds, + * that has elapsed between the two samples. * * \li\b apz.fling_repaint_interval * Maximum amount of time flinging before sending a viewport change. This will @@ -684,7 +684,14 @@ public: bool continueX = mApzc.mX.SampleOverscrollAnimation(aDelta); bool continueY = mApzc.mY.SampleOverscrollAnimation(aDelta); if (!continueX && !continueY) { - mApzc.OverscrollAnimationEnding(); + // If we got into overscroll from a fling, that fling did not request a + // fling snap to avoid a resulting scrollTo from cancelling the overscroll + // animation too early. We do still want to request a fling snap, though, + // in case the end of the axis at which we're overscrolled is not a valid + // snap point, so we request one now. If there are no snap points, this will + // do nothing. If there are snap points, we'll get a scrollTo that snaps us + // back to the nearest valid snap point. + mApzc.RequestSnap(); return false; } return true; @@ -908,7 +915,7 @@ AsyncPanZoomController::Destroy() { APZThreadUtils::AssertOnCompositorThread(); - CancelAnimation(); + CancelAnimation(CancelAnimationFlags::RequestSnap); { // scope the lock MonitorAutoLock lock(mRefPtrMonitor); @@ -1032,8 +1039,8 @@ nsEventStatus AsyncPanZoomController::HandleDragEvent(const MouseInput& aEvent, CSSPoint scrollFramePoint = aEvent.mLocalOrigin / GetFrameMetrics().GetZoom(); // The scrollbar can be transformed with the frame but the pres shell // resolution is only applied to the scroll frame. - CSSPoint scrollbarPoint = scrollFramePoint * GetFrameMetrics().GetPresShellResolution(); - CSSRect cssCompositionBound = GetFrameMetrics().GetCompositionBounds() / GetFrameMetrics().GetZoom(); + CSSPoint scrollbarPoint = scrollFramePoint * mFrameMetrics.GetPresShellResolution(); + CSSRect cssCompositionBound = mFrameMetrics.CalculateCompositedRectInCssPixels(); float mousePosition = GetAxisStart(aDragMetrics.mDirection, scrollbarPoint) - aDragMetrics.mScrollbarDragOffset - @@ -1042,34 +1049,31 @@ nsEventStatus AsyncPanZoomController::HandleDragEvent(const MouseInput& aEvent, float scrollMax = GetAxisEnd(aDragMetrics.mDirection, aDragMetrics.mScrollTrack); scrollMax -= node->GetScrollSize() / - GetAxisScale(aDragMetrics.mDirection, GetFrameMetrics().GetZoom()) * - GetFrameMetrics().GetPresShellResolution(); + GetAxisScale(aDragMetrics.mDirection, mFrameMetrics.GetZoom()) * + mFrameMetrics.GetPresShellResolution(); float scrollPercent = mousePosition / scrollMax; float minScrollPosition = - GetAxisStart(aDragMetrics.mDirection, GetFrameMetrics().GetScrollableRect().TopLeft()); + GetAxisStart(aDragMetrics.mDirection, mFrameMetrics.GetScrollableRect().TopLeft()); float maxScrollPosition = - GetAxisSize(aDragMetrics.mDirection, GetFrameMetrics().GetScrollableRect()) - - GetAxisSize(aDragMetrics.mDirection, GetFrameMetrics().GetCompositionBounds()); + GetAxisSize(aDragMetrics.mDirection, mFrameMetrics.GetScrollableRect()) - + GetAxisSize(aDragMetrics.mDirection, mFrameMetrics.GetCompositionBounds()); float scrollPosition = scrollPercent * maxScrollPosition; scrollPosition = std::max(scrollPosition, minScrollPosition); scrollPosition = std::min(scrollPosition, maxScrollPosition); - CSSPoint scrollOffset; + CSSPoint scrollOffset = mFrameMetrics.GetScrollOffset(); if (aDragMetrics.mDirection == AsyncDragMetrics::HORIZONTAL) { - scrollOffset = CSSPoint(scrollPosition, 0); + scrollOffset.x = scrollPosition; } else { - scrollOffset = CSSPoint(0, scrollPosition); + scrollOffset.y = scrollPosition; } mFrameMetrics.SetScrollOffset(scrollOffset); ScheduleCompositeAndMaybeRepaint(); UpdateSharedCompositorFrameMetrics(); - // Here we consume the events. This means that the content scrollbars - // will only see the initial mouse down and the final mouse up. - // APZ will still update the scroll position. return nsEventStatus_eConsumeNoDefault; } @@ -1123,13 +1127,13 @@ nsEventStatus AsyncPanZoomController::HandleInputEvent(const InputData& aEvent, break; } case MOUSE_INPUT: { - ScrollWheelInput scrollInput = aEvent.AsScrollWheelInput(); - if (!scrollInput.TransformToLocal(aTransformToApzc)) { + MouseInput mouseInput = aEvent.AsMouseInput(); + if (!mouseInput.TransformToLocal(aTransformToApzc)) { return rv; } // TODO Need to implement blocks to properly handle this. - //rv = HandleDragEvent(scrollInput, dragMetrics); + //rv = HandleDragEvent(mouseInput, dragMetrics); break; } case SCROLLWHEEL_INPUT: { @@ -1230,8 +1234,6 @@ nsEventStatus AsyncPanZoomController::OnTouchStart(const MultiTouchInput& aEvent case PANNING: case PANNING_LOCKED_X: case PANNING_LOCKED_Y: - case CROSS_SLIDING_X: - case CROSS_SLIDING_Y: case PINCHING: NS_WARNING("Received impossible touch in OnTouchStart"); break; @@ -1254,12 +1256,6 @@ nsEventStatus AsyncPanZoomController::OnTouchMove(const MultiTouchInput& aEvent) // second tap. Ignore the move if this happens. return nsEventStatus_eIgnore; - case CROSS_SLIDING_X: - case CROSS_SLIDING_Y: - // While cross-sliding, we don't want to consume any touchmove events for - // panning or zooming, and let the caller handle them instead. - return nsEventStatus_eIgnore; - case TOUCHING: { ScreenCoord panThreshold = GetTouchStartTolerance(); UpdateWithTouchAtDevicePoint(aEvent); @@ -1331,16 +1327,22 @@ nsEventStatus AsyncPanZoomController::OnTouchEnd(const MultiTouchInput& aEvent) return nsEventStatus_eIgnore; case TOUCHING: - case CROSS_SLIDING_X: - case CROSS_SLIDING_Y: // We may have some velocity stored on the axis from move events // that were not big enough to trigger scrolling. Clear that out. mX.SetVelocity(0); mY.SetVelocity(0); - // It's possible we may be overscrolled if the user tapped during a - // previous overscroll pan. Make sure to snap back in this situation. - if (!SnapBackIfOverscrolled()) { - SetState(NOTHING); + APZC_LOG("%p still has %u touch points active\n", this, + CurrentTouchBlock()->GetActiveTouchCount()); + // In cases where the user is panning, then taps the second finger without + // entering a pinch, we will arrive here when the second finger is lifted. + // However the first finger is still down so we want to remain in state + // TOUCHING. + if (CurrentTouchBlock()->GetActiveTouchCount() == 0) { + // It's possible we may be overscrolled if the user tapped during a + // previous overscroll pan. Make sure to snap back in this situation. + if (!SnapBackIfOverscrolled()) { + SetState(NOTHING); + } } return nsEventStatus_eIgnore; @@ -1535,6 +1537,10 @@ nsEventStatus AsyncPanZoomController::OnScaleEnd(const PinchGestureInput& aEvent } else { ClearOverscroll(); } + // Along with clearing the overscroll, we also want to snap to the nearest + // snap point as appropriate, so ask the main thread (which knows about such + // things) to handle it. + RequestSnap(); ScheduleComposite(); RequestContentRepaint(); @@ -1749,12 +1755,9 @@ nsEventStatus AsyncPanZoomController::OnScrollWheel(const ScrollWheelInput& aEve switch (aEvent.mScrollMode) { case ScrollWheelInput::SCROLLMODE_INSTANT: { - // Call ToScreenCoordinates outside the lock because it grabs the tree lock. ScreenPoint distance = ToScreenCoordinates( ParentLayerPoint(fabs(delta.x), fabs(delta.y)), aEvent.mLocalOrigin); - ReentrantMonitorAutoEnter lock(mMonitor); - CancelAnimation(); SetState(WHEEL_SCROLL); @@ -1767,6 +1770,10 @@ nsEventStatus AsyncPanZoomController::OnScrollWheel(const ScrollWheelInput& aEve CallDispatchScroll(startPoint, endPoint, handoffState); SetState(NOTHING); + + // The calls above handle their own locking; moreover, + // ToScreenCoordinates() and CallDispatchScroll() can grab the tree lock. + ReentrantMonitorAutoEnter lock(mMonitor); RequestContentRepaint(); break; @@ -1933,6 +1940,19 @@ nsEventStatus AsyncPanZoomController::OnPanEnd(const PanGestureInput& aEvent) { mX.EndTouch(aEvent.mTime); mY.EndTouch(aEvent.mTime); + + // Drop any velocity on axes where we don't have room to scroll anyways + // (in this APZC, or an APZC further in the handoff chain). + // This ensures that we don't enlarge the display port unnecessarily. + RefPtr overscrollHandoffChain = + CurrentPanGestureBlock()->GetOverscrollHandoffChain(); + if (!overscrollHandoffChain->CanScrollInDirection(this, Layer::HORIZONTAL)) { + mX.SetVelocity(0); + } + if (!overscrollHandoffChain->CanScrollInDirection(this, Layer::VERTICAL)) { + mY.SetVelocity(0); + } + SetState(NOTHING); RequestContentRepaint(); @@ -2182,24 +2202,17 @@ void AsyncPanZoomController::HandlePanning(double aAngle) { bool canScrollVertical = !mY.IsAxisLocked() && overscrollHandoffChain->CanScrollInDirection(this, Layer::VERTICAL); - if (!gfxPrefs::APZCrossSlideEnabled() && - (!canScrollHorizontal || !canScrollVertical)) { + if (!canScrollHorizontal || !canScrollVertical) { SetState(PANNING); } else if (IsCloseToHorizontal(aAngle, gfxPrefs::APZAxisLockAngle())) { mY.SetAxisLocked(true); if (canScrollHorizontal) { SetState(PANNING_LOCKED_X); - } else { - SetState(CROSS_SLIDING_X); - mX.SetAxisLocked(true); } } else if (IsCloseToVertical(aAngle, gfxPrefs::APZAxisLockAngle())) { mX.SetAxisLocked(true); if (canScrollVertical) { SetState(PANNING_LOCKED_Y); - } else { - SetState(CROSS_SLIDING_Y); - mY.SetAxisLocked(true); } } else { SetState(PANNING); @@ -2216,13 +2229,13 @@ void AsyncPanZoomController::HandlePanningUpdate(const ScreenPoint& aPanDistance float breakThreshold = gfxPrefs::APZAxisBreakoutThreshold() * APZCTreeManager::GetDPI(); if (fabs(aPanDistance.x) > breakThreshold || fabs(aPanDistance.y) > breakThreshold) { - if (mState == PANNING_LOCKED_X || mState == CROSS_SLIDING_X) { + if (mState == PANNING_LOCKED_X) { if (!IsCloseToHorizontal(angle, gfxPrefs::APZAxisBreakoutAngle())) { mY.SetAxisLocked(false); SetState(PANNING); } - } else if (mState == PANNING_LOCKED_Y || mState == CROSS_SLIDING_Y) { - if (!IsCloseToVertical(angle, gfxPrefs::APZAxisLockAngle())) { + } else if (mState == PANNING_LOCKED_Y) { + if (!IsCloseToVertical(angle, gfxPrefs::APZAxisBreakoutAngle())) { mX.SetAxisLocked(false); SetState(PANNING); } @@ -2568,6 +2581,8 @@ void AsyncPanZoomController::CancelAnimation(CancelAnimationFlags aFlags) { bool repaint = !IsZero(GetVelocityVector()); mX.SetVelocity(0); mY.SetVelocity(0); + mX.SetAxisLocked(false); + mY.SetAxisLocked(false); // Setting the state to nothing and cancelling the animation can // preempt normal mechanisms for relieving overscroll, so we need to clear // overscroll here. @@ -2575,6 +2590,11 @@ void AsyncPanZoomController::CancelAnimation(CancelAnimationFlags aFlags) { ClearOverscroll(); repaint = true; } + // Similar to relieving overscroll, we also need to snap to any snap points + // if appropriate, so ask the main thread to do that. + if (aFlags & CancelAnimationFlags::RequestSnap) { + RequestSnap(); + } if (repaint) { RequestContentRepaint(); ScheduleComposite(); @@ -2617,15 +2637,20 @@ static CSSSize CalculateDisplayPortSize(const CSSSize& aCompositionSize, const CSSPoint& aVelocity) { - float xMultiplier = fabsf(aVelocity.x) < gfxPrefs::APZMinSkateSpeed() + bool xIsStationarySpeed = fabsf(aVelocity.x) < gfxPrefs::APZMinSkateSpeed(); + bool yIsStationarySpeed = fabsf(aVelocity.y) < gfxPrefs::APZMinSkateSpeed(); + float xMultiplier = xIsStationarySpeed ? gfxPrefs::APZXStationarySizeMultiplier() : gfxPrefs::APZXSkateSizeMultiplier(); - float yMultiplier = fabsf(aVelocity.y) < gfxPrefs::APZMinSkateSpeed() + float yMultiplier = yIsStationarySpeed ? gfxPrefs::APZYStationarySizeMultiplier() : gfxPrefs::APZYSkateSizeMultiplier(); - if (IsHighMemSystem()) { + if (IsHighMemSystem() && !xIsStationarySpeed) { xMultiplier += gfxPrefs::APZXSkateHighMemAdjust(); + } + + if (IsHighMemSystem() && !yIsStationarySpeed) { yMultiplier += gfxPrefs::APZYSkateHighMemAdjust(); } @@ -2745,6 +2770,12 @@ bool AsyncPanZoomController::SnapBackIfOverscrolled() { StartOverscrollAnimation(ParentLayerPoint(0, 0)); return true; } + // If we don't kick off an overscroll animation, we still need to ask the + // main thread to snap to any nearby snap points, assuming we haven't already + // done so when we started this fling + if (mState != FLING) { + RequestSnap(); + } return false; } @@ -3314,13 +3345,16 @@ void AsyncPanZoomController::ZoomToRect(CSSRect aRect) { targetZoom = CSSToParentLayerScale(std::min(compositionBounds.width / aRect.width, compositionBounds.height / aRect.height)); } - // 1. If the rect is empty, request received from browserElementScrolling.js + // 1. If the rect is empty, the content-side logic for handling a double-tap + // requested that we zoom out. // 2. currentZoom is equal to mZoomConstraints.mMaxZoom and user still double-tapping it // 3. currentZoom is equal to localMinZoom and user still double-tapping it // Treat these three cases as a request to zoom out as much as possible. + bool zoomOut = false; if (aRect.IsEmpty() || (currentZoom == localMaxZoom && targetZoom >= localMaxZoom) || (currentZoom == localMinZoom && targetZoom <= localMinZoom)) { + zoomOut = true; CSSSize compositedSize = mFrameMetrics.CalculateCompositedSizeInCssPixels(); float y = scrollOffset.y; float newHeight = @@ -3354,6 +3388,14 @@ void AsyncPanZoomController::ZoomToRect(CSSRect aRect) { aRect.x = aRect.x > 0 ? aRect.x : 0; } + // Vertically center the zoomed element in the screen. + if (!zoomOut && (sizeAfterZoom.height > aRect.height)) { + aRect.y -= (sizeAfterZoom.height - aRect.height) * 0.5f; + if (aRect.y < 0.0f) { + aRect.y = 0.0f; + } + } + endZoomToMetrics.SetScrollOffset(aRect.TopLeft()); endZoomToMetrics.SetDisplayPortMargins( CalculatePendingDisplayPort(endZoomToMetrics, ParentLayerPoint(0,0))); @@ -3390,11 +3432,8 @@ AsyncPanZoomController::CurrentPanGestureBlock() const } void -AsyncPanZoomController::ResetInputState() +AsyncPanZoomController::ResetTouchInputState() { - // This may be called during non-touch input blocks as well. We send - // a fake cancel touch event here but on the assumption that none of the - // code in GEL assumes a CurrentTouchBlock() MultiTouchInput cancel(MultiTouchInput::MULTITOUCH_CANCEL, 0, TimeStamp::Now(), 0); RefPtr listener = GetGestureEventListener(); if (listener) { @@ -3408,7 +3447,7 @@ AsyncPanZoomController::CancelAnimationAndGestureState() { mX.CancelGesture(); mY.CancelGesture(); - CancelAnimation(); + CancelAnimation(CancelAnimationFlags::RequestSnap); } bool @@ -3573,15 +3612,10 @@ void AsyncPanZoomController::ShareCompositorFrameMetrics() { } } -void AsyncPanZoomController::OverscrollAnimationEnding() { - // If we got into overscroll from a fling, that fling did not request a - // fling snap to avoid a resulting scrollTo from cancelling the overscroll - // animation too early. We do still want to request a fling snap, though, - // in case the end of the axis at which we're overscrolled is not a valid - // snap point, so we request one now. If there are no snap points, this will - // do nothing. If there are snap points, we'll get a scrollTo that snaps us - // back to the nearest valid snap point. +void AsyncPanZoomController::RequestSnap() { if (RefPtr controller = GetGeckoContentController()) { + APZC_LOG("%p requesting snap near %s\n", this, + Stringify(mFrameMetrics.GetScrollOffset()).c_str()); controller->RequestFlingSnap(mFrameMetrics.GetScrollId(), mFrameMetrics.GetScrollOffset()); } diff --git a/gfx/layers/apz/src/AsyncPanZoomController.h b/gfx/layers/apz/src/AsyncPanZoomController.h index a339a7b8cd..ad968c878b 100644 --- a/gfx/layers/apz/src/AsyncPanZoomController.h +++ b/gfx/layers/apz/src/AsyncPanZoomController.h @@ -636,9 +636,10 @@ protected: // Common processing at the end of a touch block. void OnTouchEndOrCancel(); - // This is called by OverscrollAnimation to notify us when the overscroll - // animation is ending. - void OverscrollAnimationEnding(); + // This is called to request that the main thread snap the scroll position + // to a nearby snap position if appropriate. The current scroll position is + // used as the final destination. + void RequestSnap(); uint64_t mLayersId; RefPtr mCompositorParent; @@ -741,11 +742,6 @@ protected: PAN_MOMENTUM, /* like PANNING, but controlled by momentum PanGestureInput events */ - CROSS_SLIDING_X, /* Panning disabled while user does a horizontal gesture - on a vertically-scrollable view. This used for the - Windows Metro "cross-slide" gesture. */ - CROSS_SLIDING_Y, /* as above for Y axis */ - PINCHING, /* nth touch-start, where n > 1. this mode allows pan and zoom */ ANIMATING_ZOOM, /* animated zoom to a new rect */ OVERSCROLL_ANIMATION, /* Spring-based animation used to relieve overscroll once @@ -805,9 +801,9 @@ public: bool ArePointerEventsConsumable(TouchBlockState* aBlock, uint32_t aTouchPoints); /** - * Clear internal state relating to input handling. + * Clear internal state relating to touch input handling. */ - void ResetInputState(); + void ResetTouchInputState(); private: void CancelAnimationAndGestureState(); diff --git a/gfx/layers/apz/src/Axis.cpp b/gfx/layers/apz/src/Axis.cpp index e02e336270..8b9a0cd111 100644 --- a/gfx/layers/apz/src/Axis.cpp +++ b/gfx/layers/apz/src/Axis.cpp @@ -76,7 +76,7 @@ void Axis::UpdateWithTouchAtDevicePoint(ParentLayerCoord aPos, ParentLayerCoord return; } - float newVelocity = mAxisLocked ? 0.0f : (float)(mPos - aPos - aAdditionalDelta) / (float)(aTimestampMs - mPosTimeMs); + float newVelocity = mAxisLocked ? 0.0f : (float)(mPos - aPos + aAdditionalDelta) / (float)(aTimestampMs - mPosTimeMs); if (gfxPrefs::APZMaxVelocity() > 0.0f) { bool velocityIsNegative = (newVelocity < 0); newVelocity = fabs(newVelocity); @@ -382,6 +382,7 @@ void Axis::EndTouch(uint32_t aTimestampMs) { // mVelocityQueue is controller-thread only APZThreadUtils::AssertOnControllerThread(); + mAxisLocked = false; mVelocity = 0; int count = 0; while (!mVelocityQueue.IsEmpty()) { diff --git a/gfx/layers/apz/src/InputBlockState.cpp b/gfx/layers/apz/src/InputBlockState.cpp index 0d8725ed6c..924ed7cfff 100644 --- a/gfx/layers/apz/src/InputBlockState.cpp +++ b/gfx/layers/apz/src/InputBlockState.cpp @@ -887,5 +887,11 @@ TouchBlockState::UpdateSlopState(const MultiTouchInput& aInput, return mInSlop; } +uint32_t +TouchBlockState::GetActiveTouchCount() const +{ + return mTouchCounter.GetActiveTouchCount(); +} + } // namespace layers } // namespace mozilla diff --git a/gfx/layers/apz/src/InputBlockState.h b/gfx/layers/apz/src/InputBlockState.h index 14ba191c69..98a8bb9cb8 100644 --- a/gfx/layers/apz/src/InputBlockState.h +++ b/gfx/layers/apz/src/InputBlockState.h @@ -453,6 +453,11 @@ public: bool UpdateSlopState(const MultiTouchInput& aInput, bool aApzcCanConsumeEvents); + /** + * Returns the number of touch points currently active. + */ + uint32_t GetActiveTouchCount() const; + bool HasEvents() const override; void DropEvents() override; void HandleEvents() override; diff --git a/gfx/layers/apz/src/InputQueue.cpp b/gfx/layers/apz/src/InputQueue.cpp index 6b04bf3d6a..7b62a7ff00 100644 --- a/gfx/layers/apz/src/InputQueue.cpp +++ b/gfx/layers/apz/src/InputQueue.cpp @@ -661,7 +661,9 @@ InputQueue::ProcessInputBlocks() { curBlock->DropEvents(); } else if (curBlock->IsDefaultPrevented()) { curBlock->DropEvents(); - target->ResetInputState(); + if (curBlock->AsTouchBlock()) { + target->ResetTouchInputState(); + } } else { UpdateActiveApzc(curBlock->GetTargetApzc()); curBlock->HandleEvents(); @@ -687,7 +689,7 @@ void InputQueue::UpdateActiveApzc(const RefPtr& aNewActive) { if (mLastActiveApzc && mLastActiveApzc != aNewActive && mTouchCounter.GetActiveTouchCount() > 0) { - mLastActiveApzc->ResetInputState(); + mLastActiveApzc->ResetTouchInputState(); } mLastActiveApzc = aNewActive; } diff --git a/gfx/layers/apz/test/gtest/TestAsyncPanZoomController.cpp b/gfx/layers/apz/test/gtest/TestAsyncPanZoomController.cpp index 1c1173f5f5..d6179837a3 100644 --- a/gfx/layers/apz/test/gtest/TestAsyncPanZoomController.cpp +++ b/gfx/layers/apz/test/gtest/TestAsyncPanZoomController.cpp @@ -917,6 +917,81 @@ TEST_F(APZCPinchGestureDetectorTester, Pinch_PreventDefault) { apzc->AssertStateIsReset(); } +TEST_F(APZCGestureDetectorTester, Pan_With_Tap) { + SCOPED_GFX_PREF(TouchActionEnabled, bool, false); + + FrameMetrics originalMetrics = GetPinchableFrameMetrics(); + apzc->SetFrameMetrics(originalMetrics); + + // Making the APZC zoomable isn't really needed for the correct operation of + // this test, but it could help catch regressions where we accidentally enter + // a pinch state. + MakeApzcZoomable(); + + // Test parameters + int touchX = 250; + int touchY = 300; + int panDistance = 20; + + int firstFingerId = 0; + int secondFingerId = firstFingerId + 1; + + // Put finger down + MultiTouchInput mti = MultiTouchInput(MultiTouchInput::MULTITOUCH_START, 0, TimeStamp(), 0); + mti.mTouches.AppendElement(CreateSingleTouchData(firstFingerId, touchX, touchY)); + apzc->ReceiveInputEvent(mti, nullptr); + + // Start a pan, break through the threshold + touchY += 40; + mti = MultiTouchInput(MultiTouchInput::MULTITOUCH_MOVE, 0, TimeStamp(), 0); + mti.mTouches.AppendElement(CreateSingleTouchData(firstFingerId, touchX, touchY)); + apzc->ReceiveInputEvent(mti, nullptr); + + // Do an actual pan for a bit + touchY += panDistance; + mti = MultiTouchInput(MultiTouchInput::MULTITOUCH_MOVE, 0, TimeStamp(), 0); + mti.mTouches.AppendElement(CreateSingleTouchData(firstFingerId, touchX, touchY)); + apzc->ReceiveInputEvent(mti, nullptr); + + // Put a second finger down + mti = MultiTouchInput(MultiTouchInput::MULTITOUCH_START, 0, TimeStamp(), 0); + mti.mTouches.AppendElement(CreateSingleTouchData(firstFingerId, touchX, touchY)); + mti.mTouches.AppendElement(CreateSingleTouchData(secondFingerId, touchX + 10, touchY)); + apzc->ReceiveInputEvent(mti, nullptr); + + // Lift the second finger + mti = MultiTouchInput(MultiTouchInput::MULTITOUCH_END, 0, TimeStamp(), 0); + mti.mTouches.AppendElement(CreateSingleTouchData(secondFingerId, touchX + 10, touchY)); + apzc->ReceiveInputEvent(mti, nullptr); + + // Bust through the threshold again + touchY += 40; + mti = MultiTouchInput(MultiTouchInput::MULTITOUCH_MOVE, 0, TimeStamp(), 0); + mti.mTouches.AppendElement(CreateSingleTouchData(firstFingerId, touchX, touchY)); + apzc->ReceiveInputEvent(mti, nullptr); + + // Do some more actual panning + touchY += panDistance; + mti = MultiTouchInput(MultiTouchInput::MULTITOUCH_MOVE, 0, TimeStamp(), 0); + mti.mTouches.AppendElement(CreateSingleTouchData(firstFingerId, touchX, touchY)); + apzc->ReceiveInputEvent(mti, nullptr); + + // Lift the first finger + mti = MultiTouchInput(MultiTouchInput::MULTITOUCH_END, 0, TimeStamp(), 0); + mti.mTouches.AppendElement(CreateSingleTouchData(firstFingerId, touchX, touchY)); + apzc->ReceiveInputEvent(mti, nullptr); + + // Verify that we scrolled + FrameMetrics finalMetrics = apzc->GetFrameMetrics(); + float zoom = finalMetrics.GetZoom().ToScaleFactor().scale; + EXPECT_EQ(originalMetrics.GetScrollOffset().y - (panDistance * 2 / zoom), finalMetrics.GetScrollOffset().y); + + // Clear out any remaining fling animation and pending tasks + apzc->AdvanceAnimationsUntilEnd(); + while (mcc->RunThroughDelayedTasks()); + apzc->AssertStateIsReset(); +} + TEST_F(APZCBasicTester, Overzoom) { // the visible area of the document in CSS pixels is x=10 y=0 w=100 h=100 FrameMetrics fm; diff --git a/gfx/thebes/gfxPrefs.h b/gfx/thebes/gfxPrefs.h index ea161cc082..f73bb05648 100644 --- a/gfx/thebes/gfxPrefs.h +++ b/gfx/thebes/gfxPrefs.h @@ -146,7 +146,6 @@ private: DECL_GFX_PREF(Live, "apz.axis_lock.lock_angle", APZAxisLockAngle, float, float(M_PI / 6.0) /* 30 degrees */); DECL_GFX_PREF(Live, "apz.axis_lock.mode", APZAxisLockMode, int32_t, 0); DECL_GFX_PREF(Live, "apz.content_response_timeout", APZContentResponseTimeout, int32_t, 300); - DECL_GFX_PREF(Live, "apz.cross_slide.enabled", APZCrossSlideEnabled, bool, false); DECL_GFX_PREF(Live, "apz.danger_zone_x", APZDangerZoneX, int32_t, 50); DECL_GFX_PREF(Live, "apz.danger_zone_y", APZDangerZoneY, int32_t, 100); DECL_GFX_PREF(Live, "apz.drag.enabled", APZDragEnabled, bool, false); diff --git a/modules/libpref/init/all.js b/modules/libpref/init/all.js index c37b074700..b89e9fabf0 100644 --- a/modules/libpref/init/all.js +++ b/modules/libpref/init/all.js @@ -595,7 +595,6 @@ pref("apz.axis_lock.breakout_threshold", "0.03125"); // 1/32 inches pref("apz.axis_lock.breakout_angle", "0.3926991"); // PI / 8 (22.5 degrees) pref("apz.axis_lock.direct_pan_angle", "1.047197"); // PI / 3 (60 degrees) pref("apz.content_response_timeout", 300); -pref("apz.cross_slide.enabled", false); pref("apz.drag.enabled", false); pref("apz.danger_zone_x", 50); pref("apz.danger_zone_y", 100);