author | Carsten "Tomcat" Book <cbook@mozilla.com> |
Tue, 19 Jan 2016 12:00:45 +0100 | |
changeset 280494 | b67316254602a63bf4e568198a5c7d3288a9db27 |
parent 280493 | c4e56cd70b034e2c402ca9a4c180598fa0e8277f (current diff) |
parent 280433 | 20fdd2a2344e129897217b42468cc5c85dc3f76d (diff) |
child 280495 | 6043c53cc03e82f71d6a1e1be792b985f1e7a030 |
child 280590 | 2e50b83954e62d52d2ef294e850c4380d457d96a |
child 280664 | 88cb02abee76f781ca1cee7e799897dd74f95919 |
child 280698 | 5f1246a49f86d34747efac27d9d1c65c52b95169 |
push id | 70460 |
push user | cbook@mozilla.com |
push date | Tue, 19 Jan 2016 11:05:19 +0000 |
treeherder | mozilla-inbound@6043c53cc03e [default view] [failures only] |
perfherder | [talos] [build metrics] [platform microbench] (compared to previous push) |
reviewers | merge |
milestone | 46.0a1 |
first release with | nightly linux32
b67316254602
/
46.0a1
/
20160119030232
/
files
nightly linux64
b67316254602
/
46.0a1
/
20160119030232
/
files
nightly mac
b67316254602
/
46.0a1
/
20160119030232
/
files
nightly win32
b67316254602
/
46.0a1
/
20160119030232
/
files
nightly win64
b67316254602
/
46.0a1
/
20160119030232
/
files
|
last release without | nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
|
releases | nightly linux32
46.0a1
/
20160119030232
/
pushlog to previous
nightly linux64
46.0a1
/
20160119030232
/
pushlog to previous
nightly mac
46.0a1
/
20160119030232
/
pushlog to previous
nightly win32
46.0a1
/
20160119030232
/
pushlog to previous
nightly win64
46.0a1
/
20160119030232
/
pushlog to previous
|
gfx/layers/apz/test/gtest/TestAsyncPanZoomController.cpp | file | annotate | diff | comparison | revisions | |
mobile/android/app/mobile.js | file | annotate | diff | comparison | revisions |
--- a/accessible/tests/mochitest/text/test_lineboundary.html +++ b/accessible/tests/mochitest/text/test_lineboundary.html @@ -231,25 +231,25 @@ two words <iframe id="ht_3" src="data:text/html,<div contentEditable='true'>foo<br/><br/></div>"></iframe> <p id="ht_4">Hello world </p> <ul id="ul1"> <li id="li1">Item</li> <li id="li2"></li> - <li id="li3" style="width:10ex; font-family:monospace; font-size:10pt;">a long and winding road that lead me to your door</li> + <li id="li3" style="font-family:monospace; font-size:10pt; width:8ch;">a long and winding road that lead me to your door</li> <li id="li4">a <a href=''>b</a> c</li> <li id="li5"><br>hello</li> </ul> <ol id="ol1"> <li id="li6">Item</li> <li id="li7"></li> - <li id="li8" style="width:10ex; font-family:monospace; font-size:10pt;">a long and winding road that lead me to your door</li> + <li id="li8" style="font-family:monospace; font-size:10pt; width:8ch;">a long and winding road that lead me to your door</li> <li id="li9">a <a href=''>b</a> c</li> <li id="li10"><br>hello</li> </ol> <div id="ht_5"> <div> <p>sectiounus</p> <p>seciofarus</p>
--- a/caps/nsScriptSecurityManager.cpp +++ b/caps/nsScriptSecurityManager.cpp @@ -396,22 +396,44 @@ nsScriptSecurityManager::GetChannelURIPr // as nsDocument::Reset and XULDocument::StartDocumentLoad. nsCOMPtr<nsIURI> uri; nsresult rv = NS_GetFinalChannelURI(aChannel, getter_AddRefs(uri)); NS_ENSURE_SUCCESS(rv, rv); nsCOMPtr<nsILoadContext> loadContext; NS_QueryNotificationCallbacks(aChannel, loadContext); - if (loadContext) { - return GetLoadContextCodebasePrincipal(uri, loadContext, aPrincipal); + nsCOMPtr<nsILoadInfo> loadInfo; + aChannel->GetLoadInfo(getter_AddRefs(loadInfo)); + nsContentPolicyType contentPolicyType = nsIContentPolicy::TYPE_INVALID; + if (loadInfo) { + contentPolicyType = loadInfo->GetExternalContentPolicyType(); } - //TODO: Bug 1211590. inherit Origin Attributes from LoadInfo. - PrincipalOriginAttributes attrs(UNKNOWN_APP_ID, false); + PrincipalOriginAttributes attrs; + if (nsIContentPolicy::TYPE_DOCUMENT == contentPolicyType || + nsIContentPolicy::TYPE_SUBDOCUMENT == contentPolicyType) { + // If it's document or sub-document, inherit originAttributes from + // the document. + if (loadContext) { + DocShellOriginAttributes docShellAttrs; + loadContext->GetOriginAttributes(docShellAttrs); + attrs.InheritFromDocShellToDoc(docShellAttrs, uri); + } + } else { + // Inherit origin attributes from loading principal if any. + nsCOMPtr<nsIPrincipal> loadingPrincipal; + if (loadInfo) { + loadInfo->GetLoadingPrincipal(getter_AddRefs(loadingPrincipal)); + } + if (loadingPrincipal) { + attrs = BasePrincipal::Cast(loadingPrincipal)->OriginAttributesRef(); + } + } + rv = MaybeSetAddonIdFromURI(attrs, uri); NS_ENSURE_SUCCESS(rv, rv); nsCOMPtr<nsIPrincipal> prin = BasePrincipal::CreateCodebasePrincipal(uri, attrs); prin.forget(aPrincipal); return *aPrincipal ? NS_OK : NS_ERROR_FAILURE; } NS_IMETHODIMP
--- a/docshell/base/nsDocShell.cpp +++ b/docshell/base/nsDocShell.cpp @@ -4006,16 +4006,17 @@ nsDocShell::AddChild(nsIDocShellTreeItem childDocShell->SetUseGlobalHistory(true); } if (aChild->ItemType() != mItemType) { return NS_OK; } aChild->SetTreeOwner(mTreeOwner); + childDocShell->SetUserContextId(mUserContextId); nsCOMPtr<nsIDocShell> childAsDocShell(do_QueryInterface(aChild)); if (!childAsDocShell) { return NS_OK; } // charset, style-disabling, and zoom will be inherited in SetupNewViewer() @@ -13853,16 +13854,27 @@ nsDocShell::SetIsSignedPackage(const nsA mSignedPkg = aSignedPkg; return NS_OK; } NS_IMETHODIMP nsDocShell::SetUserContextId(uint32_t aUserContextId) { mUserContextId = aUserContextId; + + nsTObserverArray<nsDocLoader*>::ForwardIterator iter(mChildList); + while (iter.HasMore()) { + nsCOMPtr<nsIDocShell> docshell = do_QueryObject(iter.GetNext()); + if (!docshell || docshell->ItemType() != ItemType()) { + continue; + } + + docshell->SetUserContextId(aUserContextId); + } + return NS_OK; } /* [infallible] */ NS_IMETHODIMP nsDocShell::GetIsBrowserElement(bool* aIsBrowser) { *aIsBrowser = (mFrameType == eFrameTypeBrowser); return NS_OK;
--- a/dom/animation/Animation.cpp +++ b/dom/animation/Animation.cpp @@ -791,47 +791,38 @@ Animation::ComposeStyle(RefPtr<AnimValue // that case the compositor might have been ahead of the main thread so we // should use the current wallclock time to ensure the animation doesn't // temporarily jump backwards. // // To address each of these cases we temporarily tweak the hold time // immediately before updating the style rule and then restore it immediately // afterwards. This is purely to prevent visual flicker. Other behavior // such as dispatching events continues to rely on the regular timeline time. - bool updatedHoldTime = false; AnimationPlayState playState = PlayState(); { AutoRestore<Nullable<TimeDuration>> restoreHoldTime(mHoldTime); if (playState == AnimationPlayState::Pending && mHoldTime.IsNull() && !mStartTime.IsNull()) { Nullable<TimeDuration> timeToUse = mPendingReadyTime; if (timeToUse.IsNull() && mTimeline && mTimeline->TracksWallclockTime()) { timeToUse = mTimeline->ToTimelineTime(TimeStamp::Now()); } if (!timeToUse.IsNull()) { mHoldTime.SetValue((timeToUse.Value() - mStartTime.Value()) .MultDouble(mPlaybackRate)); - // Push the change down to the effect - UpdateEffect(); - updatedHoldTime = true; } } mEffect->ComposeStyle(aStyleRule, aSetProperties); } - // Now that the hold time has been restored, update the effect - if (updatedHoldTime) { - UpdateEffect(); - } - MOZ_ASSERT(playState == PlayState(), "Play state should not change during the course of compositing"); mFinishedAtLastComposeStyle = (playState == AnimationPlayState::Finished); } void Animation::NotifyEffectTimingUpdated() {
--- a/dom/animation/EffectCompositor.cpp +++ b/dom/animation/EffectCompositor.cpp @@ -560,16 +560,19 @@ EffectCompositor::ComposeAnimationRule(d // As a result, we iterate from last animation to first and, if a // property has already been set, we don't change it. nsCSSPropertySet properties; for (KeyframeEffectReadOnly* effect : Reversed(sortedEffectList)) { effect->GetAnimation()->ComposeStyle(animationRule, properties); } + MOZ_ASSERT(effects == EffectSet::GetEffectSet(aElement, aPseudoType), + "EffectSet should not change while composing style"); + effects->UpdateAnimationRuleRefreshTime(aCascadeLevel, aRefreshTime); } /* static */ void EffectCompositor::GetOverriddenProperties(nsStyleContext* aStyleContext, EffectSet& aEffectSet, nsCSSPropertySet& aPropertiesOverridden)
--- a/dom/base/Element.cpp +++ b/dom/base/Element.cpp @@ -1775,18 +1775,17 @@ Element::UnbindFromTree(bool aDeep, bool } // Ensure that CSS transitions don't continue on an element at a // different place in the tree (even if reinserted before next // animation refresh). // We need to delete the properties while we're still in document // (if we were in document). // FIXME (Bug 522599): Need a test for this. - //XXXsmaug this looks slow. - if (HasFlag(NODE_HAS_PROPERTIES)) { + if (MayHaveAnimations()) { DeleteProperty(nsGkAtoms::transitionsOfBeforeProperty); DeleteProperty(nsGkAtoms::transitionsOfAfterProperty); DeleteProperty(nsGkAtoms::transitionsProperty); DeleteProperty(nsGkAtoms::animationsOfBeforeProperty); DeleteProperty(nsGkAtoms::animationsOfAfterProperty); DeleteProperty(nsGkAtoms::animationsProperty); }
--- a/dom/base/nsContentUtils.cpp +++ b/dom/base/nsContentUtils.cpp @@ -8238,16 +8238,34 @@ nsContentUtils::InternalStorageAllowedFo } // Check if we are in private browsing, and record that fact if (IsInPrivateBrowsing(document)) { access = StorageAccess::ePrivateBrowsing; } } + nsCOMPtr<nsIPermissionManager> permissionManager = + services::GetPermissionManager(); + if (!permissionManager) { + return StorageAccess::eDeny; + } + + // check the permission manager for any allow or deny permissions + // for cookies for the window. + uint32_t perm; + permissionManager->TestPermissionFromPrincipal(aPrincipal, "cookie", &perm); + if (perm == nsIPermissionManager::DENY_ACTION) { + return StorageAccess::eDeny; + } else if (perm == nsICookiePermission::ACCESS_SESSION) { + return std::min(access, StorageAccess::eSessionScoped); + } else if (perm == nsIPermissionManager::ALLOW_ACTION) { + return access; + } + // Check if we should only allow storage for the session, and record that fact if (sCookiesLifetimePolicy == nsICookieService::ACCEPT_SESSION) { // Storage could be StorageAccess::ePrivateBrowsing or StorageAccess::eAllow // so perform a std::min comparison to make sure we preserve ePrivateBrowsing // if it has been set. access = std::min(StorageAccess::eSessionScoped, access); } @@ -8279,34 +8297,16 @@ nsContentUtils::InternalStorageAllowedFo if (NS_SUCCEEDED(rv) && uri) { bool isAbout = false; MOZ_ALWAYS_TRUE(NS_SUCCEEDED(uri->SchemeIs("about", &isAbout))); if (isAbout) { return access; } } - nsCOMPtr<nsIPermissionManager> permissionManager = - services::GetPermissionManager(); - if (!permissionManager) { - return StorageAccess::eDeny; - } - - // check the permission manager for any allow or deny permissions - // for cookies for the window. - uint32_t perm; - permissionManager->TestPermissionFromPrincipal(aPrincipal, "cookie", &perm); - if (perm == nsIPermissionManager::DENY_ACTION) { - return StorageAccess::eDeny; - } else if (perm == nsICookiePermission::ACCESS_SESSION) { - return std::min(access, StorageAccess::eSessionScoped); - } else if (perm == nsIPermissionManager::ALLOW_ACTION) { - return access; - } - // We don't want to prompt for every attempt to access permissions. if (sCookiesBehavior == nsICookieService::BEHAVIOR_REJECT) { return StorageAccess::eDeny; } // In the absense of a window, we assume that we are first-party. if (aWindow && (sCookiesBehavior == nsICookieService::BEHAVIOR_REJECT_FOREIGN || sCookiesBehavior == nsICookieService::BEHAVIOR_LIMIT_FOREIGN)) {
--- a/dom/base/nsGkAtomList.h +++ b/dom/base/nsGkAtomList.h @@ -1217,16 +1217,17 @@ GK_ATOM(top, "top") GK_ATOM(topleft, "topleft") GK_ATOM(topmargin, "topmargin") GK_ATOM(toppadding, "toppadding") GK_ATOM(topright, "topright") GK_ATOM(tr, "tr") GK_ATOM(track, "track") GK_ATOM(trailing, "trailing") GK_ATOM(transform, "transform") +GK_ATOM(transform_3d, "transform-3d") GK_ATOM(transformiix, "transformiix") GK_ATOM(translate, "translate") GK_ATOM(transparent, "transparent") GK_ATOM(tree, "tree") GK_ATOM(treecell, "treecell") GK_ATOM(treechildren, "treechildren") GK_ATOM(treecol, "treecol") GK_ATOM(treecolpicker, "treecolpicker")
--- a/dom/camera/DOMCameraControl.cpp +++ b/dom/camera/DOMCameraControl.cpp @@ -524,17 +524,17 @@ nsDOMCameraControl::GetCameraStream() co return mInput; } void nsDOMCameraControl::TrackCreated(TrackID aTrackID) { // This track is not connected through a port. MediaInputPort* inputPort = nullptr; dom::VideoStreamTrack* track = - new dom::VideoStreamTrack(this, aTrackID); + new dom::VideoStreamTrack(this, aTrackID, nsString()); RefPtr<TrackPort> port = new TrackPort(inputPort, track, TrackPort::InputPortOwnership::OWNED); mTracks.AppendElement(port.forget()); NotifyTrackAdded(track); } #define THROW_IF_NO_CAMERACONTROL(...) \
--- a/dom/html/HTMLCanvasElement.cpp +++ b/dom/html/HTMLCanvasElement.cpp @@ -677,17 +677,17 @@ HTMLCanvasElement::CaptureStream(const O TrackID videoTrackId = 1; nsresult rv = stream->Init(aFrameRate, videoTrackId); if (NS_FAILED(rv)) { aRv.Throw(rv); return nullptr; } - stream->CreateOwnDOMTrack(videoTrackId, MediaSegment::VIDEO); + stream->CreateOwnDOMTrack(videoTrackId, MediaSegment::VIDEO, nsString()); rv = RegisterFrameCaptureListener(stream->FrameCaptureListener()); if (NS_FAILED(rv)) { aRv.Throw(rv); return nullptr; } return stream.forget();
--- a/dom/html/HTMLMediaElement.cpp +++ b/dom/html/HTMLMediaElement.cpp @@ -1885,21 +1885,21 @@ HTMLMediaElement::CaptureStreamInternal( mAudioCaptured = true; if (mDecoder) { mDecoder->AddOutputStream(out->mStream->GetInputStream()->AsProcessedStream(), aFinishWhenEnded); if (mReadyState >= HAVE_METADATA) { // Expose the tracks to JS directly. if (HasAudio()) { TrackID audioTrackId = mMediaInfo.mAudio.mTrackId; - out->mStream->CreateOwnDOMTrack(audioTrackId, MediaSegment::AUDIO); + out->mStream->CreateOwnDOMTrack(audioTrackId, MediaSegment::AUDIO, nsString()); } if (HasVideo()) { TrackID videoTrackId = mMediaInfo.mVideo.mTrackId; - out->mStream->CreateOwnDOMTrack(videoTrackId, MediaSegment::VIDEO); + out->mStream->CreateOwnDOMTrack(videoTrackId, MediaSegment::VIDEO, nsString()); } } } RefPtr<DOMMediaStream> result = out->mStream; return result.forget(); } already_AddRefed<DOMMediaStream>
--- a/dom/media/AudioStreamTrack.h +++ b/dom/media/AudioStreamTrack.h @@ -9,18 +9,18 @@ #include "MediaStreamTrack.h" #include "DOMMediaStream.h" namespace mozilla { namespace dom { class AudioStreamTrack : public MediaStreamTrack { public: - AudioStreamTrack(DOMMediaStream* aStream, TrackID aTrackID) - : MediaStreamTrack(aStream, aTrackID) {} + AudioStreamTrack(DOMMediaStream* aStream, TrackID aTrackID, const nsString& aLabel) + : MediaStreamTrack(aStream, aTrackID, aLabel) {} virtual JSObject* WrapObject(JSContext* aCx, JS::Handle<JSObject*> aGivenProto) override; virtual AudioStreamTrack* AsAudioStreamTrack() override { return this; } // WebIDL virtual void GetKind(nsAString& aKind) override { aKind.AssignLiteral("audio"); } };
--- a/dom/media/DOMMediaStream.cpp +++ b/dom/media/DOMMediaStream.cpp @@ -123,17 +123,17 @@ public: if (track) { // This track has already been manually created. Abort. return; } NS_WARN_IF_FALSE(!mStream->mTracks.IsEmpty(), "A new track was detected on the input stream; creating a corresponding MediaStreamTrack. " "Initial tracks should be added manually to immediately and synchronously be available to JS."); - mStream->CreateOwnDOMTrack(aTrackId, aType); + mStream->CreateOwnDOMTrack(aTrackId, aType, nsString()); } void DoNotifyTrackEnded(TrackID aTrackId) { MOZ_ASSERT(NS_IsMainThread()); if (!mStream) { return; @@ -614,17 +614,17 @@ DOMMediaStream::InitAudioCaptureStream(n { mWindow = aWindow; const TrackID AUDIO_TRACK = 1; InitInputStreamCommon(aGraph->CreateAudioCaptureStream(this, AUDIO_TRACK), aGraph); InitOwnedStreamCommon(aGraph); InitPlaybackStreamCommon(aGraph); - CreateOwnDOMTrack(AUDIO_TRACK, MediaSegment::AUDIO); + CreateOwnDOMTrack(AUDIO_TRACK, MediaSegment::AUDIO, nsString()); } void DOMMediaStream::InitInputStreamCommon(MediaStream* aStream, MediaStreamGraph* aGraph) { MOZ_ASSERT(!mOwnedStream, "Input stream must be initialized before owned stream"); @@ -775,30 +775,30 @@ DOMMediaStream::AddPrincipalChangeObserv bool DOMMediaStream::RemovePrincipalChangeObserver(PrincipalChangeObserver* aObserver) { return mPrincipalChangeObservers.RemoveElement(aObserver); } MediaStreamTrack* -DOMMediaStream::CreateOwnDOMTrack(TrackID aTrackID, MediaSegment::Type aType) +DOMMediaStream::CreateOwnDOMTrack(TrackID aTrackID, MediaSegment::Type aType, const nsString& aLabel) { MOZ_RELEASE_ASSERT(mInputStream); MOZ_RELEASE_ASSERT(mOwnedStream); MOZ_ASSERT(FindOwnedDOMTrack(GetOwnedStream(), aTrackID) == nullptr); MediaStreamTrack* track; switch (aType) { case MediaSegment::AUDIO: - track = new AudioStreamTrack(this, aTrackID); + track = new AudioStreamTrack(this, aTrackID, aLabel); break; case MediaSegment::VIDEO: - track = new VideoStreamTrack(this, aTrackID); + track = new VideoStreamTrack(this, aTrackID, aLabel); break; default: MOZ_CRASH("Unhandled track type"); } LOG(LogLevel::Debug, ("DOMMediaStream %p Created new track %p with ID %u", this, track, aTrackID)); RefPtr<TrackPort> ownedTrackPort =
--- a/dom/media/DOMMediaStream.h +++ b/dom/media/DOMMediaStream.h @@ -471,17 +471,17 @@ public: } /** * Called for each track in our owned stream to indicate to JS that we * are carrying that track. * * Creates a MediaStreamTrack, adds it to mTracks and returns it. */ - MediaStreamTrack* CreateOwnDOMTrack(TrackID aTrackID, MediaSegment::Type aType); + MediaStreamTrack* CreateOwnDOMTrack(TrackID aTrackID, MediaSegment::Type aType, const nsString& aLabel); class OnTracksAvailableCallback { public: virtual ~OnTracksAvailableCallback() {} virtual void NotifyTracksAvailable(DOMMediaStream* aStream) = 0; }; // When the initial set of tracks has been added, run // aCallback->NotifyTracksAvailable.
--- a/dom/media/MediaManager.cpp +++ b/dom/media/MediaManager.cpp @@ -1020,20 +1020,24 @@ public: } else { // Normal case, connect the source stream to the track union stream to // avoid us blocking domStream = nsDOMUserMediaStream::CreateSourceStream(window, mListener, mAudioDevice, mVideoDevice, msg); if (mAudioDevice) { - domStream->CreateOwnDOMTrack(kAudioTrack, MediaSegment::AUDIO); + nsString audioDeviceName; + mAudioDevice->GetName(audioDeviceName); + domStream->CreateOwnDOMTrack(kAudioTrack, MediaSegment::AUDIO, audioDeviceName); } if (mVideoDevice) { - domStream->CreateOwnDOMTrack(kVideoTrack, MediaSegment::VIDEO); + nsString videoDeviceName; + mVideoDevice->GetName(videoDeviceName); + domStream->CreateOwnDOMTrack(kVideoTrack, MediaSegment::VIDEO, videoDeviceName); } nsCOMPtr<nsIPrincipal> principal; if (mPeerIdentity) { principal = nsNullPrincipal::Create(); domStream->SetPeerIdentity(mPeerIdentity.forget()); } else { principal = window->GetExtantDoc()->NodePrincipal();
--- a/dom/media/MediaStreamTrack.cpp +++ b/dom/media/MediaStreamTrack.cpp @@ -7,18 +7,18 @@ #include "DOMMediaStream.h" #include "nsIUUIDGenerator.h" #include "nsServiceManagerUtils.h" namespace mozilla { namespace dom { -MediaStreamTrack::MediaStreamTrack(DOMMediaStream* aStream, TrackID aTrackID) - : mOwningStream(aStream), mTrackID(aTrackID), mEnded(false), mEnabled(true) +MediaStreamTrack::MediaStreamTrack(DOMMediaStream* aStream, TrackID aTrackID, const nsString& aLabel) + : mOwningStream(aStream), mTrackID(aTrackID), mLabel(aLabel), mEnded(false), mEnabled(true) { nsresult rv; nsCOMPtr<nsIUUIDGenerator> uuidgen = do_GetService("@mozilla.org/uuid-generator;1", &rv); nsID uuid; memset(&uuid, 0, sizeof(uuid));
--- a/dom/media/MediaStreamTrack.h +++ b/dom/media/MediaStreamTrack.h @@ -24,17 +24,17 @@ class VideoStreamTrack; * Class representing a track in a DOMMediaStream. */ class MediaStreamTrack : public DOMEventTargetHelper { public: /** * aTrackID is the MediaStreamGraph track ID for the track in the * MediaStream owned by aStream. */ - MediaStreamTrack(DOMMediaStream* aStream, TrackID aTrackID); + MediaStreamTrack(DOMMediaStream* aStream, TrackID aTrackID, const nsString& aLabel); NS_DECL_ISUPPORTS_INHERITED NS_DECL_CYCLE_COLLECTION_CLASS_INHERITED(MediaStreamTrack, DOMEventTargetHelper) DOMMediaStream* GetParentObject() const { return mOwningStream; } virtual JSObject* WrapObject(JSContext* aCx, JS::Handle<JSObject*> aGivenProto) override = 0; @@ -49,17 +49,17 @@ public: */ TrackID GetTrackID() const { return mTrackID; } virtual AudioStreamTrack* AsAudioStreamTrack() { return nullptr; } virtual VideoStreamTrack* AsVideoStreamTrack() { return nullptr; } // WebIDL virtual void GetKind(nsAString& aKind) = 0; void GetId(nsAString& aID) const; - void GetLabel(nsAString& aLabel) { aLabel.Truncate(); } + void GetLabel(nsAString& aLabel) { aLabel.Assign(mLabel); } bool Enabled() { return mEnabled; } void SetEnabled(bool aEnabled); void Stop(); already_AddRefed<Promise> ApplyConstraints(const dom::MediaTrackConstraints& aConstraints, ErrorResult &aRv); bool Ended() const { return mEnded; } // Notifications from the MediaStreamGraph @@ -70,16 +70,17 @@ public: void AssignId(const nsAString& aID) { mID = aID; } protected: virtual ~MediaStreamTrack(); RefPtr<DOMMediaStream> mOwningStream; TrackID mTrackID; nsString mID; + nsString mLabel; bool mEnded; bool mEnabled; }; } // namespace dom } // namespace mozilla #endif /* MEDIASTREAMTRACK_H_ */
--- a/dom/media/VideoStreamTrack.h +++ b/dom/media/VideoStreamTrack.h @@ -9,18 +9,18 @@ #include "MediaStreamTrack.h" #include "DOMMediaStream.h" namespace mozilla { namespace dom { class VideoStreamTrack : public MediaStreamTrack { public: - VideoStreamTrack(DOMMediaStream* aStream, TrackID aTrackID) - : MediaStreamTrack(aStream, aTrackID) {} + VideoStreamTrack(DOMMediaStream* aStream, TrackID aTrackID, const nsString& aLabel) + : MediaStreamTrack(aStream, aTrackID, aLabel) {} virtual JSObject* WrapObject(JSContext* aCx, JS::Handle<JSObject*> aGivenProto) override; virtual VideoStreamTrack* AsVideoStreamTrack() override { return this; } // WebIDL virtual void GetKind(nsAString& aKind) override { aKind.AssignLiteral("video"); } };
--- a/dom/media/systemservices/CamerasChild.cpp +++ b/dom/media/systemservices/CamerasChild.cpp @@ -24,84 +24,26 @@ #undef LOG_ENABLED mozilla::LazyLogModule gCamerasChildLog("CamerasChild"); #define LOG(args) MOZ_LOG(gCamerasChildLog, mozilla::LogLevel::Debug, args) #define LOG_ENABLED() MOZ_LOG_TEST(gCamerasChildLog, mozilla::LogLevel::Debug) namespace mozilla { namespace camera { -// We emulate the sync webrtc.org API with the help of singleton -// CamerasSingleton, which manages a pointer to an IPC object, a thread -// where IPC operations should run on, and a mutex. -// The static function Cameras() will use that Singleton to set up, -// if needed, both the thread and the associated IPC objects and return -// a pointer to the IPC object. Users can then do IPC calls on that object -// after dispatching them to aforementioned thread. - -// 2 Threads are involved in this code: -// - the MediaManager thread, which will call the (static, sync API) functions -// through MediaEngineRemoteVideoSource -// - the Cameras IPC thread, which will be doing our IPC to the parent process -// via PBackground - -// Our main complication is that we emulate a sync API while (having to do) -// async messaging. We dispatch the messages to another thread to send them -// async and hold a Monitor to wait for the result to be asynchronously received -// again. The requirement for async messaging originates on the parent side: -// it's not reasonable to block all PBackground IPC there while waiting for -// something like device enumeration to complete. - -class CamerasSingleton { -public: - CamerasSingleton() - : mCamerasMutex("CamerasSingleton::mCamerasMutex"), - mCameras(nullptr), - mCamerasChildThread(nullptr) { - LOG(("CamerasSingleton: %p", this)); - } - - ~CamerasSingleton() { - LOG(("~CamerasSingleton: %p", this)); - } +CamerasSingleton::CamerasSingleton() + : mCamerasMutex("CamerasSingleton::mCamerasMutex"), + mCameras(nullptr), + mCamerasChildThread(nullptr) { + LOG(("CamerasSingleton: %p", this)); +} - static CamerasSingleton& GetInstance() { - static CamerasSingleton instance; - return instance; - } - - static OffTheBooksMutex& Mutex() { - return GetInstance().mCamerasMutex; - } - - static CamerasChild*& Child() { - GetInstance().Mutex().AssertCurrentThreadOwns(); - return GetInstance().mCameras; - } - - static nsCOMPtr<nsIThread>& Thread() { - GetInstance().Mutex().AssertCurrentThreadOwns(); - return GetInstance().mCamerasChildThread; - } - -private: - // Reinitializing CamerasChild will change the pointers below. - // We don't want this to happen in the middle of preparing IPC. - // We will be alive on destruction, so this needs to be off the books. - mozilla::OffTheBooksMutex mCamerasMutex; - - // This is owned by the IPC code, and the same code controls the lifetime. - // It will set and clear this pointer as appropriate in setup/teardown. - // We'd normally make this a WeakPtr but unfortunately the IPC code already - // uses the WeakPtr mixin in a protected base class of CamerasChild, and in - // any case the object becomes unusable as soon as IPC is tearing down, which - // will be before actual destruction. - CamerasChild* mCameras; - nsCOMPtr<nsIThread> mCamerasChildThread; -}; +CamerasSingleton::~CamerasSingleton() { + LOG(("~CamerasSingleton: %p", this)); +} class InitializeIPCThread : public nsRunnable { public: InitializeIPCThread() : mCamerasChild(nullptr) {} NS_IMETHOD Run() override { @@ -130,17 +72,17 @@ public: CamerasChild* GetCamerasChild() { return mCamerasChild; } private: CamerasChild* mCamerasChild; }; -static CamerasChild* +CamerasChild* GetCamerasChild() { CamerasSingleton::Mutex().AssertCurrentThreadOwns(); if (!CamerasSingleton::Child()) { MOZ_ASSERT(!NS_IsMainThread(), "Should not be on the main Thread"); MOZ_ASSERT(!CamerasSingleton::Thread()); LOG(("No sCameras, setting up IPC Thread")); nsresult rv = NS_NewNamedThread("Cameras IPC", getter_AddRefs(CamerasSingleton::Thread())); @@ -184,27 +126,16 @@ CamerasChild::RecvReplySuccess(void) LOG((__PRETTY_FUNCTION__)); MonitorAutoLock monitor(mReplyMonitor); mReceivedReply = true; mReplySuccess = true; monitor.Notify(); return true; } -int NumberOfCapabilities(CaptureEngine aCapEngine, const char* deviceUniqueIdUTF8) -{ - OffTheBooksMutexAutoLock lock(CamerasSingleton::Mutex()); - CamerasChild* child = GetCamerasChild(); - if (child) { - return child->NumberOfCapabilities(aCapEngine, deviceUniqueIdUTF8); - } else { - return 0; - } -} - bool CamerasChild::RecvReplyNumberOfCapabilities(const int& numdev) { LOG((__PRETTY_FUNCTION__)); MonitorAutoLock monitor(mReplyMonitor); mReceivedReply = true; mReplySuccess = true; mReplyInteger = numdev; @@ -258,27 +189,16 @@ CamerasChild::NumberOfCapabilities(Captu if (!DispatchToParent(runnable, monitor)) { LOG(("Get capture capability count failed")); return 0; } LOG(("Capture capability count: %d", mReplyInteger)); return mReplyInteger; } -int NumberOfCaptureDevices(CaptureEngine aCapEngine) -{ - OffTheBooksMutexAutoLock lock(CamerasSingleton::Mutex()); - CamerasChild* child = GetCamerasChild(); - if (child) { - return child->NumberOfCaptureDevices(aCapEngine); - } else { - return 0; - } -} - int CamerasChild::NumberOfCaptureDevices(CaptureEngine aCapEngine) { MutexAutoLock requestLock(mRequestMutex); LOG((__PRETTY_FUNCTION__)); nsCOMPtr<nsIRunnable> runnable = media::NewRunnableFrom([this, aCapEngine]() -> nsresult { if (this->SendNumberOfCaptureDevices(aCapEngine)) { @@ -302,32 +222,16 @@ CamerasChild::RecvReplyNumberOfCaptureDe MonitorAutoLock monitor(mReplyMonitor); mReceivedReply = true; mReplySuccess = true; mReplyInteger = numdev; monitor.Notify(); return true; } -int GetCaptureCapability(CaptureEngine aCapEngine, const char* unique_idUTF8, - const unsigned int capability_number, - webrtc::CaptureCapability& capability) -{ - OffTheBooksMutexAutoLock lock(CamerasSingleton::Mutex()); - CamerasChild* child = GetCamerasChild(); - if (child) { - return child->GetCaptureCapability(aCapEngine, - unique_idUTF8, - capability_number, - capability); - } else { - return -1; - } -} - int CamerasChild::GetCaptureCapability(CaptureEngine aCapEngine, const char* unique_idUTF8, const unsigned int capability_number, webrtc::CaptureCapability& capability) { MutexAutoLock requestLock(mRequestMutex); LOG(("GetCaptureCapability: %s %d", unique_idUTF8, capability_number)); @@ -360,37 +264,16 @@ CamerasChild::RecvReplyGetCaptureCapabil mReplyCapability.expectedCaptureDelay = ipcCapability.expectedCaptureDelay(); mReplyCapability.rawType = static_cast<webrtc::RawVideoType>(ipcCapability.rawType()); mReplyCapability.codecType = static_cast<webrtc::VideoCodecType>(ipcCapability.codecType()); mReplyCapability.interlaced = ipcCapability.interlaced(); monitor.Notify(); return true; } - -int GetCaptureDevice(CaptureEngine aCapEngine, - unsigned int list_number, char* device_nameUTF8, - const unsigned int device_nameUTF8Length, - char* unique_idUTF8, - const unsigned int unique_idUTF8Length) -{ - OffTheBooksMutexAutoLock lock(CamerasSingleton::Mutex()); - CamerasChild* child = GetCamerasChild(); - if (child) { - return child->GetCaptureDevice(aCapEngine, - list_number, - device_nameUTF8, - device_nameUTF8Length, - unique_idUTF8, - unique_idUTF8Length); - } else { - return -1; - } -} - int CamerasChild::GetCaptureDevice(CaptureEngine aCapEngine, unsigned int list_number, char* device_nameUTF8, const unsigned int device_nameUTF8Length, char* unique_idUTF8, const unsigned int unique_idUTF8Length) { MutexAutoLock requestLock(mRequestMutex); @@ -422,33 +305,16 @@ CamerasChild::RecvReplyGetCaptureDevice( mReceivedReply = true; mReplySuccess = true; mReplyDeviceName = device_name; mReplyDeviceID = device_id; monitor.Notify(); return true; } -int AllocateCaptureDevice(CaptureEngine aCapEngine, - const char* unique_idUTF8, - const unsigned int unique_idUTF8Length, - int& capture_id) -{ - OffTheBooksMutexAutoLock lock(CamerasSingleton::Mutex()); - CamerasChild* child = GetCamerasChild(); - if (child) { - return child->AllocateCaptureDevice(aCapEngine, - unique_idUTF8, - unique_idUTF8Length, - capture_id); - } else { - return -1; - } -} - int CamerasChild::AllocateCaptureDevice(CaptureEngine aCapEngine, const char* unique_idUTF8, const unsigned int unique_idUTF8Length, int& capture_id) { MutexAutoLock requestLock(mRequestMutex); LOG((__PRETTY_FUNCTION__)); @@ -478,27 +344,16 @@ CamerasChild::RecvReplyAllocateCaptureDe MonitorAutoLock monitor(mReplyMonitor); mReceivedReply = true; mReplySuccess = true; mReplyInteger = numdev; monitor.Notify(); return true; } -int ReleaseCaptureDevice(CaptureEngine aCapEngine, const int capture_id) -{ - OffTheBooksMutexAutoLock lock(CamerasSingleton::Mutex()); - CamerasChild* child = GetCamerasChild(); - if (child) { - return child->ReleaseCaptureDevice(aCapEngine, capture_id); - } else { - return -1; - } -} - int CamerasChild::ReleaseCaptureDevice(CaptureEngine aCapEngine, const int capture_id) { MutexAutoLock requestLock(mRequestMutex); LOG((__PRETTY_FUNCTION__)); nsCOMPtr<nsIRunnable> runnable = media::NewRunnableFrom([this, aCapEngine, capture_id]() -> nsresult { @@ -534,33 +389,16 @@ CamerasChild::RemoveCallback(const Captu CapturerElement ce = mCallbacks[i]; if (ce.engine == aCapEngine && ce.id == capture_id) { mCallbacks.RemoveElementAt(i); break; } } } -int StartCapture(CaptureEngine aCapEngine, - const int capture_id, - webrtc::CaptureCapability& webrtcCaps, - webrtc::ExternalRenderer* cb) -{ - OffTheBooksMutexAutoLock lock(CamerasSingleton::Mutex()); - CamerasChild* child = GetCamerasChild(); - if (child) { - return child->StartCapture(aCapEngine, - capture_id, - webrtcCaps, - cb); - } else { - return -1; - } -} - int CamerasChild::StartCapture(CaptureEngine aCapEngine, const int capture_id, webrtc::CaptureCapability& webrtcCaps, webrtc::ExternalRenderer* cb) { MutexAutoLock requestLock(mRequestMutex); LOG((__PRETTY_FUNCTION__)); @@ -581,27 +419,16 @@ CamerasChild::StartCapture(CaptureEngine }); MonitorAutoLock monitor(mReplyMonitor); if (!DispatchToParent(runnable, monitor)) { return -1; } return 0; } -int StopCapture(CaptureEngine aCapEngine, const int capture_id) -{ - OffTheBooksMutexAutoLock lock(CamerasSingleton::Mutex()); - CamerasChild* child = GetCamerasChild(); - if (child) { - return child->StopCapture(aCapEngine, capture_id); - } else { - return -1; - } -} - int CamerasChild::StopCapture(CaptureEngine aCapEngine, const int capture_id) { MutexAutoLock requestLock(mRequestMutex); LOG((__PRETTY_FUNCTION__)); nsCOMPtr<nsIRunnable> runnable = media::NewRunnableFrom([this, aCapEngine, capture_id]() -> nsresult { if (this->SendStopCapture(aCapEngine, capture_id)) {
--- a/dom/media/systemservices/CamerasChild.h +++ b/dom/media/systemservices/CamerasChild.h @@ -2,16 +2,17 @@ /* vim: set sw=2 ts=8 et ft=cpp : */ /* 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_CamerasChild_h #define mozilla_CamerasChild_h +#include "mozilla/Move.h" #include "mozilla/Pair.h" #include "mozilla/dom/ContentChild.h" #include "mozilla/camera/PCamerasChild.h" #include "mozilla/camera/PCamerasParent.h" #include "mozilla/Mutex.h" #include "nsCOMPtr.h" // conflicts with #include of scoped_ptr.h @@ -41,43 +42,106 @@ enum CaptureEngine : int { }; struct CapturerElement { CaptureEngine engine; int id; webrtc::ExternalRenderer* callback; }; -// statically mirror webrtc.org ViECapture API -// these are called via MediaManager->MediaEngineRemoteVideoSource -// on the MediaManager thread -int NumberOfCapabilities(CaptureEngine aCapEngine, - const char* deviceUniqueIdUTF8); -int GetCaptureCapability(CaptureEngine aCapEngine, - const char* unique_idUTF8, - const unsigned int capability_number, - webrtc::CaptureCapability& capability); -int NumberOfCaptureDevices(CaptureEngine aCapEngine); -int GetCaptureDevice(CaptureEngine aCapEngine, - unsigned int list_number, char* device_nameUTF8, - const unsigned int device_nameUTF8Length, - char* unique_idUTF8, - const unsigned int unique_idUTF8Length); -int AllocateCaptureDevice(CaptureEngine aCapEngine, - const char* unique_idUTF8, - const unsigned int unique_idUTF8Length, - int& capture_id); -int ReleaseCaptureDevice(CaptureEngine aCapEngine, - const int capture_id); -int StartCapture(CaptureEngine aCapEngine, - const int capture_id, webrtc::CaptureCapability& capability, - webrtc::ExternalRenderer* func); -int StopCapture(CaptureEngine aCapEngine, const int capture_id); +// Forward declaration so we can work with pointers to it. +class CamerasChild; + +// We emulate the sync webrtc.org API with the help of singleton +// CamerasSingleton, which manages a pointer to an IPC object, a thread +// where IPC operations should run on, and a mutex. +// The static function Cameras() will use that Singleton to set up, +// if needed, both the thread and the associated IPC objects and return +// a pointer to the IPC object. Users can then do IPC calls on that object +// after dispatching them to aforementioned thread. + +// 2 Threads are involved in this code: +// - the MediaManager thread, which will call the (static, sync API) functions +// through MediaEngineRemoteVideoSource +// - the Cameras IPC thread, which will be doing our IPC to the parent process +// via PBackground + +// Our main complication is that we emulate a sync API while (having to do) +// async messaging. We dispatch the messages to another thread to send them +// async and hold a Monitor to wait for the result to be asynchronously received +// again. The requirement for async messaging originates on the parent side: +// it's not reasonable to block all PBackground IPC there while waiting for +// something like device enumeration to complete. + +class CamerasSingleton { +public: + CamerasSingleton(); + ~CamerasSingleton(); + + static OffTheBooksMutex& Mutex() { + return GetInstance().mCamerasMutex; + } + + static CamerasChild*& Child() { + Mutex().AssertCurrentThreadOwns(); + return GetInstance().mCameras; + } + + static nsCOMPtr<nsIThread>& Thread() { + Mutex().AssertCurrentThreadOwns(); + return GetInstance().mCamerasChildThread; + } + +private: + static CamerasSingleton& GetInstance() { + static CamerasSingleton instance; + return instance; + } + + // Reinitializing CamerasChild will change the pointers below. + // We don't want this to happen in the middle of preparing IPC. + // We will be alive on destruction, so this needs to be off the books. + mozilla::OffTheBooksMutex mCamerasMutex; + + // This is owned by the IPC code, and the same code controls the lifetime. + // It will set and clear this pointer as appropriate in setup/teardown. + // We'd normally make this a WeakPtr but unfortunately the IPC code already + // uses the WeakPtr mixin in a protected base class of CamerasChild, and in + // any case the object becomes unusable as soon as IPC is tearing down, which + // will be before actual destruction. + CamerasChild* mCameras; + nsCOMPtr<nsIThread> mCamerasChildThread; +}; + +// Get a pointer to a CamerasChild object we can use to do IPC with. +// This does everything needed to set up, including starting the IPC +// channel with PBackground, blocking until thats done, and starting the +// thread to do IPC on. This will fail if we're in shutdown. On success +// it will set up the CamerasSingleton. +CamerasChild* GetCamerasChild(); + +// Shut down the IPC channel and everything associated, like WebRTC. +// This is a static call because the CamerasChild object may not even +// be alive when we're called. void Shutdown(void); +// Obtain the CamerasChild object (if possible, i.e. not shutting down), +// and maintain a grip on the object for the duration of the call. +template <class MEM_FUN, class... ARGS> +int GetChildAndCall(MEM_FUN&& f, ARGS&&... args) +{ + OffTheBooksMutexAutoLock lock(CamerasSingleton::Mutex()); + CamerasChild* child = GetCamerasChild(); + if (child) { + return (child->*f)(mozilla::Forward<ARGS>(args)...); + } else { + return -1; + } +} + class CamerasChild final : public PCamerasChild { friend class mozilla::ipc::BackgroundChildImpl; public: // We are owned by the PBackground thread only. CamerasSingleton // takes a non-owning reference. NS_INLINE_DECL_REFCOUNTING(CamerasChild)
--- a/dom/media/tests/mochitest/test_enumerateDevices.html +++ b/dom/media/tests/mochitest/test_enumerateDevices.html @@ -27,25 +27,35 @@ function mustFailWith(msg, reason, const var pushPrefs = (...p) => new Promise(r => SpecialPowers.pushPrefEnv({set: p}, r)); runTest(() => pushPrefs(["media.navigator.streams.fake", true]) .then(() => navigator.mediaDevices.enumerateDevices()) .then(devices => { ok(devices.length > 0, "At least one device found"); - devices.forEach(d => { + var jsoned = JSON.parse(JSON.stringify(devices)); + is(jsoned[0].kind, devices[0].kind, "kind survived serializer"); + is(jsoned[0].deviceId, devices[0].deviceId, "deviceId survived serializer"); + return devices.reduce((p, d) => { ok(d.kind == "videoinput" || d.kind == "audioinput", "Known device kind"); is(d.deviceId.length, 44, "Correct device id length"); ok(d.label.length !== undefined, "Device label: " + d.label); is(d.groupId, "", "Don't support groupId yet"); - }); - var jsoned = JSON.parse(JSON.stringify(devices)); - is(jsoned[0].kind, devices[0].kind, "kind survived serializer"); - is(jsoned[0].deviceId, devices[0].deviceId, "deviceId survived serializer"); + var c = {}; + c[d.kind == "videoinput" ? "video" : "audio"] = { deviceId: d.deviceId }; + return p + .then(() => navigator.mediaDevices.getUserMedia(c)) + .then(stream => { + stream.getTracks().forEach(track => { + is(typeof(track.label), "string", "Label is a string") + is(track.label, d.label, "Label is the device label") + }) + }); + }, Promise.resolve()); }) // Check deviceId failure paths for video. .then(() => mustSucceed("unknown plain deviceId on video", () => navigator.mediaDevices.getUserMedia({ video: { deviceId: "unknown9qHr8B0JIbcHlbl9xR+jMbZZ8WyoPfpCXPfc=" }, }))) .then(() => mustSucceed("unknown plain deviceId on audio", () => navigator.mediaDevices.getUserMedia({
--- a/dom/media/webaudio/MediaStreamAudioDestinationNode.cpp +++ b/dom/media/webaudio/MediaStreamAudioDestinationNode.cpp @@ -29,17 +29,17 @@ MediaStreamAudioDestinationNode::MediaSt ChannelCountMode::Explicit, ChannelInterpretation::Speakers) , mDOMStream( DOMAudioNodeMediaStream::CreateTrackUnionStream(GetOwner(), this, aContext->Graph())) { // Ensure an audio track with the correct ID is exposed to JS - mDOMStream->CreateOwnDOMTrack(AudioNodeStream::AUDIO_TRACK, MediaSegment::AUDIO); + mDOMStream->CreateOwnDOMTrack(AudioNodeStream::AUDIO_TRACK, MediaSegment::AUDIO, nsString()); ProcessedMediaStream* outputStream = mDOMStream->GetInputStream()->AsProcessedStream(); MOZ_ASSERT(!!outputStream); AudioNodeEngine* engine = new AudioNodeEngine(this); mStream = AudioNodeStream::Create(aContext, engine, AudioNodeStream::EXTERNAL_OUTPUT); mPort = outputStream->AllocateInputPort(mStream, AudioNodeStream::AUDIO_TRACK);
--- a/dom/media/webrtc/MediaEngineRemoteVideoSource.cpp +++ b/dom/media/webrtc/MediaEngineRemoteVideoSource.cpp @@ -12,16 +12,22 @@ #include "CamerasChild.h" extern mozilla::LogModule* GetMediaManagerLog(); #define LOG(msg) MOZ_LOG(GetMediaManagerLog(), mozilla::LogLevel::Debug, msg) #define LOGFRAME(msg) MOZ_LOG(GetMediaManagerLog(), mozilla::LogLevel::Verbose, msg) namespace mozilla { +// These need a definition somewhere because template +// code is allowed to take their address, and they aren't +// guaranteed to have one without this. +const unsigned int MediaEngineSource::kMaxDeviceNameLength; +const unsigned int MediaEngineSource::kMaxUniqueIdLength;; + using dom::ConstrainLongRange; NS_IMPL_ISUPPORTS0(MediaEngineRemoteVideoSource) MediaEngineRemoteVideoSource::MediaEngineRemoteVideoSource( int aIndex, mozilla::camera::CaptureEngine aCapEngine, dom::MediaSourceEnum aMediaSource, const char* aMonitorName) : MediaEngineCameraVideoSource(aIndex, aMonitorName), @@ -33,20 +39,21 @@ MediaEngineRemoteVideoSource::MediaEngin } void MediaEngineRemoteVideoSource::Init() { LOG((__PRETTY_FUNCTION__)); char deviceName[kMaxDeviceNameLength]; char uniqueId[kMaxUniqueIdLength]; - if (mozilla::camera::GetCaptureDevice(mCapEngine, - mCaptureIndex, - deviceName, kMaxDeviceNameLength, - uniqueId, kMaxUniqueIdLength)) { + if (mozilla::camera::GetChildAndCall( + &mozilla::camera::CamerasChild::GetCaptureDevice, + mCapEngine, mCaptureIndex, + deviceName, kMaxDeviceNameLength, + uniqueId, kMaxUniqueIdLength)) { LOG(("Error initializing RemoteVideoSource (GetCaptureDevice)")); return; } SetName(NS_ConvertUTF8toUTF16(deviceName)); SetUUID(uniqueId); mInitDone = true; @@ -106,19 +113,19 @@ MediaEngineRemoteVideoSource::Allocate(c if (mState == kReleased) { // Note: if shared, we don't allow a later opener to affect the resolution. // (This may change depending on spec changes for Constraints/settings) if (!ChooseCapability(aConstraints, aPrefs, aDeviceId)) { return NS_ERROR_UNEXPECTED; } - if (mozilla::camera::AllocateCaptureDevice(mCapEngine, - GetUUID().get(), - kMaxUniqueIdLength, mCaptureIndex)) { + if (mozilla::camera::GetChildAndCall( + &mozilla::camera::CamerasChild::AllocateCaptureDevice, + mCapEngine, GetUUID().get(), kMaxUniqueIdLength, mCaptureIndex)) { return NS_ERROR_FAILURE; } mState = kAllocated; LOG(("Video device %d allocated", mCaptureIndex)); } else if (MOZ_LOG_TEST(GetMediaManagerLog(), mozilla::LogLevel::Debug)) { MonitorAutoLock lock(mMonitor); if (mSources.IsEmpty()) { LOG(("Video device %d reallocated", mCaptureIndex)); @@ -140,17 +147,19 @@ MediaEngineRemoteVideoSource::Deallocate --mNrAllocations; MOZ_ASSERT(mNrAllocations >= 0, "Double-deallocations are prohibited"); if (mNrAllocations == 0) { if (mState != kStopped && mState != kAllocated) { return NS_ERROR_FAILURE; } - mozilla::camera::ReleaseCaptureDevice(mCapEngine, mCaptureIndex); + mozilla::camera::GetChildAndCall( + &mozilla::camera::CamerasChild::ReleaseCaptureDevice, + mCapEngine, mCaptureIndex); mState = kReleased; LOG(("Video device %d deallocated", mCaptureIndex)); } else { LOG(("Video device %d deallocated but still in use", mCaptureIndex)); } return NS_OK; } @@ -174,18 +183,19 @@ MediaEngineRemoteVideoSource::Start(Sour if (mState == kStarted) { return NS_OK; } mImageContainer = layers::LayerManager::CreateImageContainer(); mState = kStarted; mTrackID = aID; - if (mozilla::camera::StartCapture(mCapEngine, - mCaptureIndex, mCapability, this)) { + if (mozilla::camera::GetChildAndCall( + &mozilla::camera::CamerasChild::StartCapture, + mCapEngine, mCaptureIndex, mCapability, this)) { LOG(("StartCapture failed")); return NS_ERROR_FAILURE; } return NS_OK; } nsresult @@ -212,17 +222,19 @@ MediaEngineRemoteVideoSource::Stop(mozil } mState = kStopped; // Drop any cached image so we don't start with a stale image on next // usage mImage = nullptr; } - mozilla::camera::StopCapture(mCapEngine, mCaptureIndex); + mozilla::camera::GetChildAndCall( + &mozilla::camera::CamerasChild::StopCapture, + mCapEngine, mCaptureIndex); return NS_OK; } nsresult MediaEngineRemoteVideoSource::Restart(const dom::MediaTrackConstraints& aConstraints, const MediaEnginePrefs& aPrefs, const nsString& aDeviceId) @@ -234,19 +246,22 @@ MediaEngineRemoteVideoSource::Restart(co } if (!ChooseCapability(aConstraints, aPrefs, aDeviceId)) { return NS_ERROR_NOT_AVAILABLE; } if (mState != kStarted) { return NS_OK; } - mozilla::camera::StopCapture(mCapEngine, mCaptureIndex); - if (mozilla::camera::StartCapture(mCapEngine, - mCaptureIndex, mCapability, this)) { + mozilla::camera::GetChildAndCall( + &mozilla::camera::CamerasChild::StopCapture, + mCapEngine, mCaptureIndex); + if (mozilla::camera::GetChildAndCall( + &mozilla::camera::CamerasChild::StartCapture, + mCapEngine, mCaptureIndex, mCapability, this)) { LOG(("StartCapture failed")); return NS_ERROR_FAILURE; } return NS_OK; } void MediaEngineRemoteVideoSource::NotifyPull(MediaStreamGraph* aGraph, @@ -349,17 +364,20 @@ MediaEngineRemoteVideoSource::DeliverFra } return 0; } size_t MediaEngineRemoteVideoSource::NumCapabilities() { - int num = mozilla::camera::NumberOfCapabilities(mCapEngine, GetUUID().get()); + int num = mozilla::camera::GetChildAndCall( + &mozilla::camera::CamerasChild::NumberOfCapabilities, + mCapEngine, + GetUUID().get()); if (num > 0) { return num; } switch(mMediaSource) { case dom::MediaSourceEnum::Camera: #ifdef XP_MACOSX // Mac doesn't support capabilities. @@ -428,33 +446,36 @@ MediaEngineRemoteVideoSource::ChooseCapa void MediaEngineRemoteVideoSource::GetCapability(size_t aIndex, webrtc::CaptureCapability& aOut) { if (!mHardcodedCapabilities.IsEmpty()) { MediaEngineCameraVideoSource::GetCapability(aIndex, aOut); } - mozilla::camera::GetCaptureCapability(mCapEngine, - GetUUID().get(), - aIndex, - aOut); + mozilla::camera::GetChildAndCall( + &mozilla::camera::CamerasChild::GetCaptureCapability, + mCapEngine, + GetUUID().get(), + aIndex, + aOut); } void MediaEngineRemoteVideoSource::Refresh(int aIndex) { // NOTE: mCaptureIndex might have changed when allocated! // Use aIndex to update information, but don't change mCaptureIndex!! // Caller looked up this source by uniqueId, so it shouldn't change char deviceName[kMaxDeviceNameLength]; char uniqueId[kMaxUniqueIdLength]; - if (mozilla::camera::GetCaptureDevice(mCapEngine, - aIndex, - deviceName, sizeof(deviceName), - uniqueId, sizeof(uniqueId))) { + if (mozilla::camera::GetChildAndCall( + &mozilla::camera::CamerasChild::GetCaptureDevice, + mCapEngine, aIndex, + deviceName, sizeof(deviceName), + uniqueId, sizeof(uniqueId))) { return; } SetName(NS_ConvertUTF8toUTF16(deviceName)); #ifdef DEBUG MOZ_ASSERT(GetUUID().Equals(uniqueId)); #endif }
--- a/dom/media/webrtc/MediaEngineWebRTC.cpp +++ b/dom/media/webrtc/MediaEngineWebRTC.cpp @@ -156,50 +156,57 @@ MediaEngineWebRTC::EnumerateVideoDevices * We still enumerate every time, in case a new device was plugged in since * the last call. TODO: Verify that WebRTC actually does deal with hotplugging * new devices (with or without new engine creation) and accordingly adjust. * Enumeration is not neccessary if GIPS reports the same set of devices * for a given instance of the engine. Likewise, if a device was plugged out, * mVideoSources must be updated. */ int num; - num = mozilla::camera::NumberOfCaptureDevices(capEngine); + num = mozilla::camera::GetChildAndCall( + &mozilla::camera::CamerasChild::NumberOfCaptureDevices, + capEngine); if (num <= 0) { return; } for (int i = 0; i < num; i++) { char deviceName[MediaEngineSource::kMaxDeviceNameLength]; char uniqueId[MediaEngineSource::kMaxUniqueIdLength]; // paranoia deviceName[0] = '\0'; uniqueId[0] = '\0'; int error; - error = mozilla::camera::GetCaptureDevice(capEngine, - i, deviceName, - sizeof(deviceName), uniqueId, - sizeof(uniqueId)); - + error = mozilla::camera::GetChildAndCall( + &mozilla::camera::CamerasChild::GetCaptureDevice, + capEngine, + i, deviceName, + sizeof(deviceName), uniqueId, + sizeof(uniqueId)); if (error) { LOG(("camera:GetCaptureDevice: Failed %d", error )); continue; } #ifdef DEBUG LOG((" Capture Device Index %d, Name %s", i, deviceName)); webrtc::CaptureCapability cap; - int numCaps = mozilla::camera::NumberOfCapabilities(capEngine, - uniqueId); + int numCaps = mozilla::camera::GetChildAndCall( + &mozilla::camera::CamerasChild::NumberOfCapabilities, + capEngine, + uniqueId); LOG(("Number of Capabilities %d", numCaps)); for (int j = 0; j < numCaps; j++) { - if (mozilla::camera::GetCaptureCapability(capEngine, - uniqueId, - j, cap ) != 0 ) { + if (mozilla::camera::GetChildAndCall( + &mozilla::camera::CamerasChild::GetCaptureCapability, + capEngine, + uniqueId, + j, cap) != 0) { break; } LOG(("type=%d width=%d height=%d maxFPS=%d", cap.rawType, cap.width, cap.height, cap.maxFPS )); } #endif if (uniqueId[0] == '\0') {
--- a/dom/settings/SettingsDB.jsm +++ b/dom/settings/SettingsDB.jsm @@ -35,17 +35,17 @@ const TYPED_ARRAY_THINGS = new Set([ "Uint16Array", "Int32Array", "Uint32Array", "Float32Array", "Float64Array", ]); this.SETTINGSDB_NAME = "settings"; -this.SETTINGSDB_VERSION = 7; +this.SETTINGSDB_VERSION = 8; this.SETTINGSSTORE_NAME = "settings"; Cu.import("resource://gre/modules/IndexedDBHelper.jsm"); Cu.import("resource://gre/modules/FileUtils.jsm"); Cu.import("resource://gre/modules/NetUtil.jsm"); this.SettingsDB = function SettingsDB() {}
new file mode 100644 --- /dev/null +++ b/dom/tests/mochitest/localstorage/frameLocalStorageSessionOnly.html @@ -0,0 +1,8 @@ +<!doctype html> +<html> + <body> + <script> + parent.postMessage(SpecialPowers.wrap(localStorage).isSessionOnly, "*"); + </script> + </body> +</html>
--- a/dom/tests/mochitest/localstorage/mochitest.ini +++ b/dom/tests/mochitest/localstorage/mochitest.ini @@ -11,16 +11,17 @@ support-files = frameQuotaSessionOnly.html frameReplace.html frameSlaveEqual.html frameSlaveNotEqual.html interOriginFrame.js interOriginTest.js interOriginTest2.js localStorageCommon.js + frameLocalStorageSessionOnly.html [test_appIsolation.html] skip-if = buildapp == 'b2g' || toolkit == 'android' || e10s #bug 793211 # b2g(needs https to work) b2g-debug(needs https to work) b2g-desktop(needs https to work) [test_brokenUTF-16.html] [test_bug600307-DBOps.html] [test_bug746272-1.html] [test_bug746272-2.html] skip-if = os == "android" || toolkit == 'gonk' # bug 962029 @@ -51,8 +52,9 @@ skip-if = buildapp == 'b2g' || toolkit = [test_localStorageQuotaSessionOnly.html] skip-if = buildapp == 'b2g' || toolkit == 'android' #TIMED_OUT # b2g(needs https support) [test_localStorageQuotaSessionOnly2.html] skip-if = buildapp == 'b2g' || toolkit == 'android' #TIMED_OUT # b2g(needs https support) [test_localStorageReplace.html] skip-if = (buildapp == 'b2g' && (toolkit != 'gonk' || debug)) || toolkit == 'android' [test_lowDeviceStorage.html] [test_storageConstructor.html] +[test_localStorageSessionPrefOverride.html]
new file mode 100644 --- /dev/null +++ b/dom/tests/mochitest/localstorage/test_localStorageSessionPrefOverride.html @@ -0,0 +1,54 @@ +<html> + <head> + <title>Local Storage Session Pref Override</title> + <script type="text/javascript" src="/tests/SimpleTest/SimpleTest.js"></script> + <script type="text/javascript" src="/tests/SimpleTest/SpawnTask.js"></script> + <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" /> + <script> + const ACCEPT_SESSION = 2; + + add_task(function*() { + yield new Promise((resolve, reject) => { + SpecialPowers.pushPrefEnv({"set": [["network.cookie.lifetimePolicy", + ACCEPT_SESSION]]}, + resolve); + }); + + // Before setting permission + yield new Promise((resolve) => { + var frame = document.createElement('iframe'); + frame.src = "frameLocalStorageSessionOnly.html"; + + var listener = (e) => { + is(e.data, true, "Before adding permission should be session only"); + window.removeEventListener('message', listener); + resolve(); + }; + window.addEventListener('message', listener); + document.body.appendChild(frame); + }); + + // After setting permission + yield new Promise((resolve) => { + SpecialPowers.pushPermissions([{"type": "cookie", "allow": 1, "context": document}], + resolve); + }); + + yield new Promise((resolve) => { + var frame = document.createElement('iframe'); + frame.src = "frameLocalStorageSessionOnly.html"; + + var listener = (e) => { + is(e.data, false, "After adding permission should not be session only"); + window.removeEventListener('message', listener); + resolve(); + }; + window.addEventListener('message', listener); + document.body.appendChild(frame); + }); + }); + </script> + </head> + <body> + </body> +</html>
--- a/dom/tests/mochitest/pointerlock/file_infiniteMovement.html +++ b/dom/tests/mochitest/pointerlock/file_infiniteMovement.html @@ -44,55 +44,63 @@ https://bugzilla.mozilla.org/show_bug.cg "Should have moved more than one screen's worth in width." + "TotalX: " + totalMovementX + " Screensize X: " + div.getBoundingClientRect().width); ok(totalMovementY > div.getBoundingClientRect().height, "Should have moved more than one screen's worth in height." + "TotalY: " + totalMovementY + " Screensize Y: " + div.getBoundingClientRect().height); } var firstMoveListener = function (e) { + info("Got first mousemove"); div.removeEventListener("mousemove", firstMoveListener, false); div.addEventListener("mousemove", secondMoveListener, false); synthesizeMouse(div,(divCenterWidth/2) * 3, (divCenterHeight/2) * 3, { type: "mousemove" }, window); } var secondMoveListener = function (e) { + info("Got second mousemove"); totalMovementX = divCenterWidth + ((divCenterWidth / 2) * 3); totalMovementY = divCenterHeight + ((divCenterHeight / 2) * 3); div.removeEventListener("mousemove", secondMoveListener, false); document.mozCancelFullScreen(); } document.addEventListener("mozpointerlockchange", function (e) { if (document.mozPointerLockElement === div) { + info("Got mozpointerlockchange for entering"); div.addEventListener("mousemove", firstMoveListener, false); divCenterWidth = Math.round(div.getBoundingClientRect().width / 2); divCenterHeight = Math.round(div.getBoundingClientRect().height / 2); synthesizeMouse(div, divCenterWidth, divCenterHeight, { type: "mousemove" }, window); + } else { + info("Got mozpointerlockchange for exiting"); } }, false); document.addEventListener("mozfullscreenchange", function() { if (document.mozFullScreenElement === div) { + info("Got mozfullscreenchange for entering"); div.mozRequestPointerLock(); } else { + info("Got mozfullscreenchange for exiting"); runTests(); SimpleTest.finish(); } }, false); function start() { + info("Requesting fullscreen on parent"); div.mozRequestFullScreen(); } </script> </pre> </body> </html>
--- a/dom/tests/mochitest/pointerlock/file_movementXY.html +++ b/dom/tests/mochitest/pointerlock/file_movementXY.html @@ -52,16 +52,17 @@ https://bugzilla.mozilla.org/show_bug.cg "mozMovementY should exist in mouse events objects."); is(secondMove.mozMovementX, secondMove.screenX - firstMove.screenX, "mozMovementX should be equal to eNow.screenX-ePrevious.screenX"); is(secondMove.mozMovementY, secondMove.screenY - firstMove.screenY, "mozMovementY should be equal to eNow.screenY-ePrevious.screenY"); } var moveMouse = function(e) { + info("Got mouse move"); mozMovementX = ("mozMovementX" in e); mozMovementY = ("mozMovementY" in e); div.removeEventListener("mousemove", moveMouse, false); div.addEventListener("mousemove", moveMouseAgain, false); firstMove.screenX = e.screenX; firstMove.screenY = e.screenY; @@ -70,45 +71,50 @@ https://bugzilla.mozilla.org/show_bug.cg divCenterHeight = Math.round(div.getBoundingClientRect().height / 2); synthesizeMouse(div, (divCenterWidth + 10), (divCenterHeight + 10), { type: "mousemove" }, window); }; var moveMouseAgain = function(e) { + info("Got mouse move again"); secondMove.screenX = e.screenX; secondMove.screenY = e.screenY; secondMove.mozMovementX = e.mozMovementX; secondMove.mozMovementY = e.mozMovementY; div.removeEventListener("mousemove", moveMouseAgain, false); document.mozCancelFullScreen(); }; function fullscreenchange() { if (document.mozFullScreenElement === div) { + info("Got mozfullscreenchange for entering"); var screenX = window.screenX; var screenY = window.screenY; if (screenX != 0 || screenY != 0) { todo(screenX == 0 && screenY == 0, "We should only receive fullscreenchange once we've finished fullscreen transition"); setTimeout(fullscreenchange, 250); return; } + info("Finish waiting for fullscreenchange"); div.addEventListener("mousemove", moveMouse, false); synthesizeMouseAtCenter(div, {type: "mousemove"}, window); } else { + info("Got mozfullscreenchange for exiting"); runTests(); SimpleTest.finish(); } } document.addEventListener("mozfullscreenchange", fullscreenchange, false); function start() { + info("Requesting fullscreen on parent"); div.mozRequestFullScreen(); } </script> </pre> </body> </html>
--- a/dom/tests/mochitest/pointerlock/file_pointerlock-api.html +++ b/dom/tests/mochitest/pointerlock/file_pointerlock-api.html @@ -44,57 +44,63 @@ ok(hasExitPointerLock, "Document should have mozExitPointerLock"); ok(pointerLockElement, "Document should keep track of correct pointer locked element"); ok(hasMovementX, "Mouse Event should have mozMovementX."); ok(hasMovementY, "Mouse Event should have mozMovementY."); ok(!gotContextMenuEvent, "Shouldn't have got a contextmenu event."); } function mouseMoveHandler(e) { + info("Got mousemove"); document.removeEventListener("mousemove", mouseMoveHandler, false); hasMovementX = "mozMovementX" in e; hasMovementY = "mozMovementY" in e; hasExitPointerLock = "mozExitPointerLock" in document; document.mozExitPointerLock(); } document.addEventListener("mozpointerlockchange", function (e) { pointerLockChangeEventFired = true; if (document.mozPointerLockElement) { + info("Got mozpointerlockchange for entering"); pointerLocked = true; pointerLockElement = document.mozPointerLockElement === div; window.addEventListener("contextmenu", function() { gotContextMenuEvent = true; }, true); synthesizeMouse(document.body, 4, 4, { type: "contextmenu", button: 2 }, window); document.addEventListener("mousemove", mouseMoveHandler, false); synthesizeMouseAtCenter(div, {type: "mousemove"}, window); } else { + info("Got mozpointerlockchange for exiting"); pointerUnlocked = true; document.mozCancelFullScreen(); } }, false); document.addEventListener("mozfullscreenchange", function(e) { if (document.mozFullScreenElement === div) { + info("Got mozfullscreenchange for entering"); hasRequestPointerLock = "mozRequestPointerLock" in div; div.mozRequestPointerLock(); } else { + info("Got mozfullscreenchange for exiting"); runTests(); SimpleTest.finish(); } }, false); function start() { div = document.getElementById("div"); + info("Requesting fullscreen on parent"); div.mozRequestFullScreen(); } </script> </pre> </body> </html>
--- a/dom/tests/mochitest/pointerlock/file_retargetMouseEvents.html +++ b/dom/tests/mochitest/pointerlock/file_retargetMouseEvents.html @@ -88,108 +88,122 @@ https://bugzilla.mozilla.org/show_bug.cg }; var childWheelTest = function() { childStats.wheel = true; }; // Event listeners for the parent element var startMouseTests = function() { + info("Got parent mousemove"); parent.removeEventListener("mousemove", startMouseTests); parent.addEventListener("DOMMouseScroll", parentScrollTest); child.addEventListener("DOMMouseScroll", childScrollTest); SimpleTest.executeSoon(function () { synthesizeWheel(child, 5, 5, {'deltaY': 10, 'lineOrPageDeltaY': 10, 'deltaMode': WheelEvent.DOM_DELTA_LINE}); }); }; var parentScrollTest = function (e) { + info("Got parent DOMMouseScroll"); parentStats.mouseScroll = true; parent.removeEventListener("DOMMouseScroll", parentScrollTest); child.removeEventListener("DOMMouseScroll", childScrollTest); parent.addEventListener("wheel", parentWheelTest); child.addEventListener("wheel", childWheelTest); SimpleTest.executeSoon(function () { synthesizeWheel(child, 5, 5, {'deltaY': 10, 'lineOrPageDeltaY': 10, 'deltaMode': WheelEvent.DOM_DELTA_LINE}); }); }; var parentWheelTest = function (e) { + info("Got parent wheel"); parentStats.wheel = true; parent.removeEventListener("wheel", parentWheelTest); child.removeEventListener("wheel", childWheelTest); parent.addEventListener("mousedown", parentDownTest); child.addEventListener("mousedown", childDownTest); SimpleTest.executeSoon(function () { synthesizeMouseAtCenter(child, {type: "mousedown"}, window); }); }; var parentDownTest = function (e) { + info("Got parent mousedown"); parentStats.mouseDown = true; parent.removeEventListener("mousedown", parentDownTest); child.removeEventListener("mousedown", childDownTest); parent.addEventListener("mouseup", parentUpTest); child.addEventListener("mouseup", childUpTest); SimpleTest.executeSoon(function () { synthesizeMouseAtCenter(child, {type: "mouseup"}, window); }); }; var parentUpTest = function (e) { + info("Got parent mouseup"); parentStats.mouseUp = true; parent.removeEventListener("mouseup", parentUpTest); child.removeEventListener("mouseup", childUpTest); parent.addEventListener("click", parentClickTest); child.addEventListener("click", childClickTest); SimpleTest.executeSoon(function () { synthesizeMouseAtCenter(child, {}, window); }); }; var parentClickTest = function (e) { + info("Got parent click"); parentStats.mouseClick = true; parent.removeEventListener("click", parentClickTest); child.removeEventListener("click", childClickTest); parent.addEventListener("mousemove", parentMoveTest); child.addEventListener("mousemove", childMoveTest); SimpleTest.executeSoon(function () { synthesizeMouseAtCenter(child, {type: "mousemove"}, window); }); }; var parentMoveTest = function (e) { + info("Got parent mousemove"); parentStats.mouseMove = true; parent.removeEventListener("mousemove", parentMoveTest); child.removeEventListener("mousemove", childMoveTest); SimpleTest.executeSoon(function () { + info("Exit fullscreen"); document.mozCancelFullScreen(); }); } document.addEventListener("mozpointerlockchange", function (e) { if (document.mozPointerLockElement === parent) { + info("Got mozpointerlockchange for entering"); parent.addEventListener("mousemove", startMouseTests); child.addEventListener("mousemove", childMoveTest); SimpleTest.executeSoon(function () { synthesizeMouseAtCenter(parent, {type: "mousemove"}, window); }); + } else { + info("Got mozpointerlockchange for exiting"); } }, false); document.addEventListener("mozfullscreenchange", function (e) { if (document.mozFullScreenElement === parent) { + info("Got mozfullscreenchange for entering"); parent.mozRequestPointerLock(); } else { + info("Got mozfullscreenchange for exiting"); runTests(); SimpleTest.finish(); } }, false); function start() { + info("Requesting fullscreen on parent"); parent.mozRequestFullScreen(); } </script> </pre> </body> </html>
--- a/dom/tests/mochitest/pointerlock/file_screenClientXYConst.html +++ b/dom/tests/mochitest/pointerlock/file_screenClientXYConst.html @@ -44,16 +44,17 @@ https://bugzilla.mozilla.org/show_bug.cg "clientY should be equal to where the mouse was originaly locked"); is(unLockedCoords.screenX, lockedCoords.screenX, "screenX should be equal to where the mouse was originaly locked"); is(unLockedCoords.screenY, lockedCoords.screenY, "screenY should be equal to where the mouse was originaly locked"); } function moveUnlocked(e) { + info("Got mousemove via moveUnlocked"); var firstCall = !unLockedCoords; if (!firstCall) { todo(false, "mousemove is fired twice."); } unLockedCoords = { screenX: e.screenX, screenY: e.screenY, @@ -65,61 +66,69 @@ https://bugzilla.mozilla.org/show_bug.cg return; } isUnlocked = !document.mozPointerLockElement; div.mozRequestPointerLock(); } function moveLocked(e) { + info("Got mousemove via moveLocked"); div.removeEventListener("mousemove", moveLocked, false); isLocked = !!document.mozPointerLockElement; lockedCoords = { screenX: e.screenX, screenY: e.screenY, clientX: e.clientX, clientY: e.clientY }; document.mozCancelFullScreen(); } document.addEventListener("mozpointerlockchange", function (e) { if (document.mozPointerLockElement === div) { + info("Got mozpointerlockchange for entering"); div.removeEventListener("mousemove", moveUnlocked, false); div.addEventListener("mousemove", moveLocked, false); divRect = div.getBoundingClientRect(); synthesizeNativeMouseMove(div, (divRect.width / 4) * 3, (divRect.height / 4) * 3); + } else { + info("Got mozpointerlockchange for exiting"); } }, false); function fullscreenchange() { var screenX = window.screenX; var screenY = window.screenY; if (document.mozFullScreenElement === div) { + info("Got mozfullscreenchange for entering"); if (screenX != 0 || screenY != 0) { todo(screenX == 0 && screenY == 0, "We should only receive fullscreenchange once we've finished fullscreen transition " + "window.screenX=" + screenX + " window.screenY=" + screenY); setTimeout(fullscreenchange, 250); return; } + info("Finish waiting for fullscreenchange"); synthesizeNativeMouseMove(div, 0, 0, () => { div.addEventListener("mousemove", moveUnlocked, false); divRect = div.getBoundingClientRect(); synthesizeNativeMouseMove(div, divRect.width / 2, divRect.height / 2); }); } else { + info("Got mozfullscreenchange for exiting"); runTests(); SimpleTest.finish(); } } document.addEventListener("mozfullscreenchange", fullscreenchange, false); function start() { div = document.getElementById("div"); + info("Requesting fullscreen on parent"); div.mozRequestFullScreen(); } </script> </body> </html>
--- a/dom/tests/mochitest/pointerlock/pointerlock_utils.js +++ b/dom/tests/mochitest/pointerlock/pointerlock_utils.js @@ -27,16 +27,20 @@ if (window.opener) { window.todo_is = function(a, b, msg) { opener.todo_is(a, b, testName + ": " + msg); }; window.todo_isnot = function(a, b, msg) { opener.todo_isnot(a, b, testName + ": " + msg); }; + window.info = function(msg) { + opener.info(testName + ": " + msg); + }; + // Override bits of SimpleTest so test files work stand-alone var SimpleTest = SimpleTest || {}; SimpleTest.waitForExplicitFinish = function() { dump("[POINTERLOCK] Starting " + testName+ "\n"); }; SimpleTest.finish = function () {
--- a/dom/webidl/Storage.webidl +++ b/dom/webidl/Storage.webidl @@ -24,9 +24,12 @@ interface Storage { [Throws] setter creator void setItem(DOMString key, DOMString value); [Throws] deleter void removeItem(DOMString key); [Throws] void clear(); + + [ChromeOnly] + readonly attribute boolean isSessionOnly; };
--- a/gfx/2d/Logging.h +++ b/gfx/2d/Logging.h @@ -378,17 +378,19 @@ public: if (MOZ_UNLIKELY(LogIt())) { mMessage << "Matrix(" << aMatrix._11 << " " << aMatrix._12 << " ; " << aMatrix._21 << " " << aMatrix._22 << " ; " << aMatrix._31 << " " << aMatrix._32 << ")"; } return *this; } template<typename T> Log &operator<<(Hexa<T> aHex) { if (MOZ_UNLIKELY(LogIt())) { - mMessage << "0x" << std::hex << aHex.mVal << std::dec; + mMessage << std::showbase << std::hex + << aHex.mVal + << std::noshowbase << std::dec; } return *this; } Log& operator<<(SurfaceFormat aFormat) { if (MOZ_UNLIKELY(LogIt())) { switch(aFormat) { case SurfaceFormat::B8G8R8A8:
--- a/gfx/layers/Compositor.cpp +++ b/gfx/layers/Compositor.cpp @@ -352,16 +352,38 @@ DecomposeIntoNoRepeatRects(const gfx::Re flipped); SetRects(3, aLayerRects, aTextureRects, xmid, ymid, aRect.XMost(), aRect.YMost(), 0.0f, 0.0f, br.x, br.y, flipped); return 4; } +gfx::IntRect +Compositor::ComputeBackdropCopyRect(const gfx::Rect& aRect, + const gfx::Rect& aClipRect, + const gfx::Matrix4x4& aTransform) +{ + gfx::Rect renderBounds = mRenderBounds; + + // Compute the clip. + renderBounds.IntersectRect(renderBounds, aClipRect); + + // Apply the layer transform. + gfx::Rect dest = aTransform.TransformAndClipBounds(aRect, renderBounds); + dest.RoundOut(); + + gfx::IntRect result; + dest.ToIntRect(&result); + + gfx::IntPoint offset = GetCurrentRenderTarget()->GetOrigin(); + result.MoveBy(-offset); + return result; +} + #if defined(MOZ_WIDGET_GONK) && ANDROID_VERSION >= 17 void Compositor::SetDispAcquireFence(Layer* aLayer, nsIWidget* aWidget) { // OpenGL does not provide ReleaseFence for rendering. // Instead use DispAcquireFence as layer buffer's ReleaseFence // to prevent flickering and tearing. // DispAcquireFence is DisplaySurface's AcquireFence.
--- a/gfx/layers/Compositor.h +++ b/gfx/layers/Compositor.h @@ -114,16 +114,17 @@ class Matrix; class DrawTarget; } // namespace gfx namespace layers { struct Effect; struct EffectChain; class Image; +class ImageHostOverlay; class Layer; class TextureSource; class DataTextureSource; class CompositingRenderTarget; class PCompositorParent; class LayerManagerComposite; enum SurfaceInitMode @@ -448,16 +449,22 @@ public: * composite. Returns false if rendering should be aborted. */ virtual bool Ready() { return true; } // XXX I expect we will want to move mWidget into this class and implement // these methods properly. virtual nsIWidget* GetWidget() const { return nullptr; } + virtual bool HasImageHostOverlays() { return false; } + + virtual void AddImageHostOverlay(ImageHostOverlay* aOverlay) {} + + virtual void RemoveImageHostOverlay(ImageHostOverlay* aOverlay) {} + /** * Debug-build assertion that can be called to ensure code is running on the * compositor thread. */ static void AssertOnCompositorThread(); size_t GetFillRatio() { float fillRatio = 0; @@ -503,16 +510,26 @@ protected: const gfx::Rect& aVisibleRect, const gfx::Rect& aClipRect, const gfx::Matrix4x4& transform, uint32_t aFlashCounter); bool ShouldDrawDiagnostics(DiagnosticFlags); /** + * Given a layer rect, clip, and transform, compute the area of the backdrop that + * needs to be copied for mix-blending. The output transform translates from 0..1 + * space into the backdrop rect space. + */ + gfx::IntRect ComputeBackdropCopyRect( + const gfx::Rect& aRect, + const gfx::Rect& aClipRect, + const gfx::Matrix4x4& aTransform); + + /** * Render time for the current composition. */ TimeStamp mCompositionTime; /** * When nonnull, during rendering, some compositable indicated that it will * change its rendering at this time. In order not to miss it, we composite * on every vsync until this time occurs (this is the latest such time). */ @@ -530,28 +547,55 @@ protected: size_t mPixelsPerFrame; size_t mPixelsFilled; ScreenRotation mScreenRotation; RefPtr<gfx::DrawTarget> mTarget; gfx::IntRect mTargetBounds; + gfx::Rect mRenderBounds; + #if defined(MOZ_WIDGET_GONK) && ANDROID_VERSION >= 17 FenceHandle mReleaseFenceHandle; #endif private: static LayersBackend sBackend; }; // Returns the number of rects. (Up to 4) typedef gfx::Rect decomposedRectArrayT[4]; size_t DecomposeIntoNoRepeatRects(const gfx::Rect& aRect, const gfx::Rect& aTexCoordRect, decomposedRectArrayT* aLayerRects, decomposedRectArrayT* aTextureRects); +static inline bool +BlendOpIsMixBlendMode(gfx::CompositionOp aOp) +{ + switch (aOp) { + case gfx::CompositionOp::OP_MULTIPLY: + case gfx::CompositionOp::OP_SCREEN: + case gfx::CompositionOp::OP_OVERLAY: + case gfx::CompositionOp::OP_DARKEN: + case gfx::CompositionOp::OP_LIGHTEN: + case gfx::CompositionOp::OP_COLOR_DODGE: + case gfx::CompositionOp::OP_COLOR_BURN: + case gfx::CompositionOp::OP_HARD_LIGHT: + case gfx::CompositionOp::OP_SOFT_LIGHT: + case gfx::CompositionOp::OP_DIFFERENCE: + case gfx::CompositionOp::OP_EXCLUSION: + case gfx::CompositionOp::OP_HUE: + case gfx::CompositionOp::OP_SATURATION: + case gfx::CompositionOp::OP_COLOR: + case gfx::CompositionOp::OP_LUMINOSITY: + return true; + default: + return false; + } +} + } // namespace layers } // namespace mozilla #endif /* MOZILLA_GFX_COMPOSITOR_H */
--- a/gfx/layers/apz/src/AsyncPanZoomController.cpp +++ b/gfx/layers/apz/src/AsyncPanZoomController.cpp @@ -2757,17 +2757,17 @@ void AsyncPanZoomController::FlushRepain RequestContentRepaint(); UpdateSharedCompositorFrameMetrics(); } void AsyncPanZoomController::FlushRepaintForNewInputBlock() { APZC_LOG("%p flushing repaint for new input block\n", this); ReentrantMonitorAutoEnter lock(mMonitor); - RequestContentRepaint(mFrameMetrics); + RequestContentRepaint(); UpdateSharedCompositorFrameMetrics(); } bool AsyncPanZoomController::SnapBackIfOverscrolled() { ReentrantMonitorAutoEnter lock(mMonitor); // It's possible that we're already in the middle of an overscroll // animation - if so, don't start a new one. if (IsOverscrolled() && mState != OVERSCROLL_ANIMATION) { @@ -2800,24 +2800,54 @@ bool AsyncPanZoomController::IsPannable( } int32_t AsyncPanZoomController::GetLastTouchIdentifier() const { RefPtr<GestureEventListener> listener = GetGestureEventListener(); return listener ? listener->GetLastTouchIdentifier() : -1; } void AsyncPanZoomController::RequestContentRepaint() { - RequestContentRepaint(mFrameMetrics); + // Reinvoke this method on the main thread if it's not there already. It's + // important to do this before the call to CalculatePendingDisplayPort, so + // that CalculatePendingDisplayPort uses the most recent available version of + // mFrameMetrics, just before the paint request is dispatched to content. + if (!NS_IsMainThread()) { + // use the local variable to resolve the function overload. + auto func = static_cast<void (AsyncPanZoomController::*)()> + (&AsyncPanZoomController::RequestContentRepaint); + NS_DispatchToMainThread(NS_NewRunnableMethod(this, func)); + return; + } + + MOZ_ASSERT(NS_IsMainThread()); + + ReentrantMonitorAutoEnter lock(mMonitor); + ParentLayerPoint velocity = GetVelocityVector(); + mFrameMetrics.SetDisplayPortMargins(CalculatePendingDisplayPort(mFrameMetrics, velocity)); + mFrameMetrics.SetUseDisplayPortMargins(true); + mFrameMetrics.SetPaintRequestTime(TimeStamp::Now()); + RequestContentRepaint(mFrameMetrics, velocity); } -void AsyncPanZoomController::RequestContentRepaint(FrameMetrics& aFrameMetrics) +/*static*/ CSSRect +GetDisplayPortRect(const FrameMetrics& aFrameMetrics) { - ParentLayerPoint velocity = GetVelocityVector(); - aFrameMetrics.SetDisplayPortMargins(CalculatePendingDisplayPort(aFrameMetrics, velocity)); - aFrameMetrics.SetUseDisplayPortMargins(true); + // This computation is based on what happens in CalculatePendingDisplayPort. If that + // changes then this might need to change too + CSSRect baseRect(aFrameMetrics.GetScrollOffset(), + aFrameMetrics.CalculateBoundedCompositedSizeInCssPixels()); + baseRect.Inflate(aFrameMetrics.GetDisplayPortMargins() / aFrameMetrics.DisplayportPixelsPerCSSPixel()); + return baseRect; +} + +void +AsyncPanZoomController::RequestContentRepaint(const FrameMetrics& aFrameMetrics, + const ParentLayerPoint& aVelocity) +{ + MOZ_ASSERT(NS_IsMainThread()); // If we're trying to paint what we already think is painted, discard this // request since it's a pointless paint. ScreenMargin marginDelta = (mLastPaintRequestMetrics.GetDisplayPortMargins() - aFrameMetrics.GetDisplayPortMargins()); if (fabsf(marginDelta.left) < EPSILON && fabsf(marginDelta.top) < EPSILON && fabsf(marginDelta.right) < EPSILON && @@ -2830,57 +2860,32 @@ void AsyncPanZoomController::RequestCont fabsf(aFrameMetrics.GetViewport().width - mLastPaintRequestMetrics.GetViewport().width) < EPSILON && fabsf(aFrameMetrics.GetViewport().height - mLastPaintRequestMetrics.GetViewport().height) < EPSILON && aFrameMetrics.GetScrollGeneration() == mLastPaintRequestMetrics.GetScrollGeneration()) { return; } - aFrameMetrics.SetPaintRequestTime(TimeStamp::Now()); - DispatchRepaintRequest(aFrameMetrics, velocity); - aFrameMetrics.SetPresShellId(mLastContentPaintMetrics.GetPresShellId()); -} - -/*static*/ CSSRect -GetDisplayPortRect(const FrameMetrics& aFrameMetrics) -{ - // This computation is based on what happens in CalculatePendingDisplayPort. If that - // changes then this might need to change too - CSSRect baseRect(aFrameMetrics.GetScrollOffset(), - aFrameMetrics.CalculateBoundedCompositedSizeInCssPixels()); - baseRect.Inflate(aFrameMetrics.GetDisplayPortMargins() / aFrameMetrics.DisplayportPixelsPerCSSPixel()); - return baseRect; -} - -void -AsyncPanZoomController::DispatchRepaintRequest(const FrameMetrics& aFrameMetrics, - const ParentLayerPoint& aVelocity) -{ RefPtr<GeckoContentController> controller = GetGeckoContentController(); if (!controller) { return; } APZC_LOG_FM(aFrameMetrics, "%p requesting content repaint", this); if (mCheckerboardEvent) { std::stringstream info; info << " velocity " << aVelocity; std::string str = info.str(); mCheckerboardEvent->UpdateRendertraceProperty( CheckerboardEvent::RequestedDisplayPort, GetDisplayPortRect(aFrameMetrics), str); } - if (NS_IsMainThread()) { - controller->RequestContentRepaint(aFrameMetrics); - } else { - NS_DispatchToMainThread(NS_NewRunnableMethodWithArg<FrameMetrics>( - controller, &GeckoContentController::RequestContentRepaint, aFrameMetrics)); - } + controller->RequestContentRepaint(aFrameMetrics); mExpectedGeckoMetrics = aFrameMetrics; mLastPaintRequestMetrics = aFrameMetrics; } bool AsyncPanZoomController::UpdateAnimation(const TimeStamp& aSampleTime, Vector<Task*>* aOutDeferredTasks) { APZThreadUtils::AssertOnCompositorThread(); @@ -3455,29 +3460,40 @@ void AsyncPanZoomController::ZoomToRect( 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))); - endZoomToMetrics.SetUseDisplayPortMargins(true); StartAnimation(new ZoomAnimation( mFrameMetrics.GetScrollOffset(), mFrameMetrics.GetZoom(), endZoomToMetrics.GetScrollOffset(), endZoomToMetrics.GetZoom())); // Schedule a repaint now, so the new displayport will be painted before the // animation finishes. - RequestContentRepaint(endZoomToMetrics); + ParentLayerPoint velocity(0, 0); + endZoomToMetrics.SetDisplayPortMargins( + CalculatePendingDisplayPort(endZoomToMetrics, velocity)); + endZoomToMetrics.SetUseDisplayPortMargins(true); + endZoomToMetrics.SetPaintRequestTime(TimeStamp::Now()); + if (NS_IsMainThread()) { + RequestContentRepaint(endZoomToMetrics, velocity); + } else { + // use a local var to resolve the function overload + auto func = static_cast<void (AsyncPanZoomController::*)(const FrameMetrics&, const ParentLayerPoint&)> + (&AsyncPanZoomController::RequestContentRepaint); + NS_DispatchToMainThread( + NS_NewRunnableMethodWithArgs<FrameMetrics, ParentLayerPoint>( + this, func, endZoomToMetrics, velocity)); + } } } CancelableBlockState* AsyncPanZoomController::CurrentInputBlock() const { return GetInputQueue()->CurrentBlock(); }
--- a/gfx/layers/apz/src/AsyncPanZoomController.h +++ b/gfx/layers/apz/src/AsyncPanZoomController.h @@ -572,37 +572,29 @@ protected: /** * Does any panning required due to a new touch event. */ void TrackTouch(const MultiTouchInput& aEvent); /** * Utility function to send updated FrameMetrics to Gecko so that it can paint * the displayport area. Calls into GeckoContentController to do the actual - * work. Note that only one paint request can be active at a time. If a paint - * request is made while a paint is currently happening, it gets queued up. If - * a new paint request arrives before a paint is completed, the old request - * gets discarded. + * work. This call will use the current metrics. If this function is called + * from a non-main thread, it will redispatch itself to the main thread, and + * use the latest metrics during the redispatch. */ void RequestContentRepaint(); /** - * Tell the paint throttler to request a content repaint with the given - * metrics. (Helper function used by RequestContentRepaint.) If aThrottled - * is set to false, the repaint request is sent directly without going through - * the paint throttler. In particular, the GeckoContentController::RequestContentRepaint - * function will be invoked before this function returns. + * Send the provided metrics to Gecko to trigger a repaint. This function + * may filter duplicate calls with the same metrics. This function must be + * called on the main thread. */ - void RequestContentRepaint(FrameMetrics& aFrameMetrics); - - /** - * Actually send the next pending paint request to gecko. - */ - void DispatchRepaintRequest(const FrameMetrics& aFrameMetrics, - const ParentLayerPoint& aVelocity); + void RequestContentRepaint(const FrameMetrics& aFrameMetrics, + const ParentLayerPoint& aVelocity); /** * Gets the current frame metrics. This is *not* the Gecko copy stored in the * layers code. */ const FrameMetrics& GetFrameMetrics() const; /**
new file mode 100644 --- /dev/null +++ b/gfx/layers/apz/test/gtest/APZCBasicTester.h @@ -0,0 +1,119 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set sw=2 ts=8 et 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_layers_APZCBasicTester_h +#define mozilla_layers_APZCBasicTester_h + +/** + * Defines a test fixture used for testing a single APZC. + */ + +#include "APZTestCommon.h" + +class APZCBasicTester : public ::testing::Test { +public: + explicit APZCBasicTester(AsyncPanZoomController::GestureBehavior aGestureBehavior = AsyncPanZoomController::DEFAULT_GESTURES) + : mGestureBehavior(aGestureBehavior) + { + } + +protected: + virtual void SetUp() + { + gfxPrefs::GetSingleton(); + APZThreadUtils::SetThreadAssertionsEnabled(false); + APZThreadUtils::SetControllerThread(MessageLoop::current()); + + mcc = new NiceMock<MockContentControllerDelayed>(); + tm = new TestAPZCTreeManager(mcc); + apzc = new TestAsyncPanZoomController(0, mcc, tm, mGestureBehavior); + apzc->SetFrameMetrics(TestFrameMetrics()); + } + + /** + * Get the APZC's scroll range in CSS pixels. + */ + CSSRect GetScrollRange() const + { + const FrameMetrics& metrics = apzc->GetFrameMetrics(); + return CSSRect( + metrics.GetScrollableRect().TopLeft(), + metrics.GetScrollableRect().Size() - metrics.CalculateCompositedSizeInCssPixels()); + } + + virtual void TearDown() + { + while (mcc->RunThroughDelayedTasks()); + apzc->Destroy(); + } + + void MakeApzcWaitForMainThread() + { + apzc->SetWaitForMainThread(); + } + + void MakeApzcZoomable() + { + apzc->UpdateZoomConstraints(ZoomConstraints(true, true, CSSToParentLayerScale(0.25f), CSSToParentLayerScale(4.0f))); + } + + void MakeApzcUnzoomable() + { + apzc->UpdateZoomConstraints(ZoomConstraints(false, false, CSSToParentLayerScale(1.0f), CSSToParentLayerScale(1.0f))); + } + + void PanIntoOverscroll(); + + /** + * Sample animations once, 1 ms later than the last sample. + */ + void SampleAnimationOnce() + { + const TimeDuration increment = TimeDuration::FromMilliseconds(1); + ParentLayerPoint pointOut; + AsyncTransform viewTransformOut; + mcc->AdvanceBy(increment); + apzc->SampleContentTransformForFrame(&viewTransformOut, pointOut); + } + + /** + * Sample animations until we recover from overscroll. + * @param aExpectedScrollOffset the expected reported scroll offset + * throughout the animation + */ + void SampleAnimationUntilRecoveredFromOverscroll(const ParentLayerPoint& aExpectedScrollOffset) + { + const TimeDuration increment = TimeDuration::FromMilliseconds(1); + bool recoveredFromOverscroll = false; + ParentLayerPoint pointOut; + AsyncTransform viewTransformOut; + while (apzc->SampleContentTransformForFrame(&viewTransformOut, pointOut)) { + // The reported scroll offset should be the same throughout. + EXPECT_EQ(aExpectedScrollOffset, pointOut); + + // Trigger computation of the overscroll tranform, to make sure + // no assetions fire during the calculation. + apzc->GetOverscrollTransform(); + + if (!apzc->IsOverscrolled()) { + recoveredFromOverscroll = true; + } + + mcc->AdvanceBy(increment); + } + EXPECT_TRUE(recoveredFromOverscroll); + apzc->AssertStateIsReset(); + } + + void TestOverscroll(); + + AsyncPanZoomController::GestureBehavior mGestureBehavior; + RefPtr<MockContentControllerDelayed> mcc; + RefPtr<TestAPZCTreeManager> tm; + RefPtr<TestAsyncPanZoomController> apzc; +}; + +#endif // mozilla_layers_APZCBasicTester_h
new file mode 100644 --- /dev/null +++ b/gfx/layers/apz/test/gtest/APZCTreeManagerTester.h @@ -0,0 +1,167 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set sw=2 ts=8 et 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_layers_APZCTreeManagerTester_h +#define mozilla_layers_APZCTreeManagerTester_h + +/** + * Defines a test fixture used for testing multiple APZCs interacting in + * an APZCTreeManager. + */ + +#include "APZTestCommon.h" + +class APZCTreeManagerTester : public ::testing::Test { +protected: + virtual void SetUp() { + gfxPrefs::GetSingleton(); + APZThreadUtils::SetThreadAssertionsEnabled(false); + APZThreadUtils::SetControllerThread(MessageLoop::current()); + + mcc = new NiceMock<MockContentControllerDelayed>(); + manager = new TestAPZCTreeManager(mcc); + } + + virtual void TearDown() { + while (mcc->RunThroughDelayedTasks()); + manager->ClearTree(); + } + + /** + * Sample animations once for all APZCs, 1 ms later than the last sample. + */ + void SampleAnimationsOnce() { + const TimeDuration increment = TimeDuration::FromMilliseconds(1); + ParentLayerPoint pointOut; + AsyncTransform viewTransformOut; + mcc->AdvanceBy(increment); + + for (const RefPtr<Layer>& layer : layers) { + if (TestAsyncPanZoomController* apzc = ApzcOf(layer)) { + apzc->SampleContentTransformForFrame(&viewTransformOut, pointOut); + } + } + } + + RefPtr<MockContentControllerDelayed> mcc; + + nsTArray<RefPtr<Layer> > layers; + RefPtr<LayerManager> lm; + RefPtr<Layer> root; + + RefPtr<TestAPZCTreeManager> manager; + +protected: + static void SetScrollableFrameMetrics(Layer* aLayer, FrameMetrics::ViewID aScrollId, + CSSRect aScrollableRect = CSSRect(-1, -1, -1, -1)) { + FrameMetrics metrics; + metrics.SetScrollId(aScrollId); + // By convention in this test file, START_SCROLL_ID is the root, so mark it as such. + if (aScrollId == FrameMetrics::START_SCROLL_ID) { + metrics.SetIsLayersIdRoot(true); + } + IntRect layerBound = aLayer->GetVisibleRegion().ToUnknownRegion().GetBounds(); + metrics.SetCompositionBounds(ParentLayerRect(layerBound.x, layerBound.y, + layerBound.width, layerBound.height)); + metrics.SetScrollableRect(aScrollableRect); + metrics.SetScrollOffset(CSSPoint(0, 0)); + metrics.SetPageScrollAmount(LayoutDeviceIntSize(50, 100)); + metrics.SetAllowVerticalScrollWithWheel(true); + aLayer->SetFrameMetrics(metrics); + aLayer->SetClipRect(Some(ViewAs<ParentLayerPixel>(layerBound))); + if (!aScrollableRect.IsEqualEdges(CSSRect(-1, -1, -1, -1))) { + // The purpose of this is to roughly mimic what layout would do in the + // case of a scrollable frame with the event regions and clip. This lets + // us exercise the hit-testing code in APZCTreeManager + EventRegions er = aLayer->GetEventRegions(); + IntRect scrollRect = RoundedToInt(aScrollableRect * metrics.LayersPixelsPerCSSPixel()).ToUnknownRect(); + er.mHitRegion = nsIntRegion(IntRect(layerBound.TopLeft(), scrollRect.Size())); + aLayer->SetEventRegions(er); + } + } + + void SetScrollHandoff(Layer* aChild, Layer* aParent) { + FrameMetrics metrics = aChild->GetFrameMetrics(0); + metrics.SetScrollParentId(aParent->GetFrameMetrics(0).GetScrollId()); + aChild->SetFrameMetrics(metrics); + } + + static TestAsyncPanZoomController* ApzcOf(Layer* aLayer) { + EXPECT_EQ(1u, aLayer->GetFrameMetricsCount()); + return (TestAsyncPanZoomController*)aLayer->GetAsyncPanZoomController(0); + } + + void CreateSimpleScrollingLayer() { + const char* layerTreeSyntax = "t"; + nsIntRegion layerVisibleRegion[] = { + nsIntRegion(IntRect(0,0,200,200)), + }; + root = CreateLayerTree(layerTreeSyntax, layerVisibleRegion, nullptr, lm, layers); + SetScrollableFrameMetrics(root, FrameMetrics::START_SCROLL_ID, CSSRect(0, 0, 500, 500)); + } + + void CreateSimpleDTCScrollingLayer() { + const char* layerTreeSyntax = "t"; + nsIntRegion layerVisibleRegion[] = { + nsIntRegion(IntRect(0,0,200,200)), + }; + root = CreateLayerTree(layerTreeSyntax, layerVisibleRegion, nullptr, lm, layers); + SetScrollableFrameMetrics(root, FrameMetrics::START_SCROLL_ID, CSSRect(0, 0, 500, 500)); + + EventRegions regions; + regions.mHitRegion = nsIntRegion(IntRect(0, 0, 200, 200)); + regions.mDispatchToContentHitRegion = regions.mHitRegion; + layers[0]->SetEventRegions(regions); + } + + void CreateSimpleMultiLayerTree() { + const char* layerTreeSyntax = "c(tt)"; + // LayerID 0 12 + nsIntRegion layerVisibleRegion[] = { + nsIntRegion(IntRect(0,0,100,100)), + nsIntRegion(IntRect(0,0,100,50)), + nsIntRegion(IntRect(0,50,100,50)), + }; + root = CreateLayerTree(layerTreeSyntax, layerVisibleRegion, nullptr, lm, layers); + } + + void CreatePotentiallyLeakingTree() { + const char* layerTreeSyntax = "c(c(c(t))c(c(t)))"; + // LayerID 0 1 2 3 4 5 6 + root = CreateLayerTree(layerTreeSyntax, nullptr, nullptr, lm, layers); + SetScrollableFrameMetrics(layers[0], FrameMetrics::START_SCROLL_ID); + SetScrollableFrameMetrics(layers[2], FrameMetrics::START_SCROLL_ID + 1); + SetScrollableFrameMetrics(layers[5], FrameMetrics::START_SCROLL_ID + 1); + SetScrollableFrameMetrics(layers[3], FrameMetrics::START_SCROLL_ID + 2); + SetScrollableFrameMetrics(layers[6], FrameMetrics::START_SCROLL_ID + 3); + } + + void CreateBug1194876Tree() { + const char* layerTreeSyntax = "c(t)"; + // LayerID 0 1 + nsIntRegion layerVisibleRegion[] = { + nsIntRegion(IntRect(0,0,100,100)), + nsIntRegion(IntRect(0,0,100,100)), + }; + root = CreateLayerTree(layerTreeSyntax, layerVisibleRegion, nullptr, lm, layers); + SetScrollableFrameMetrics(layers[0], FrameMetrics::START_SCROLL_ID); + SetScrollableFrameMetrics(layers[1], FrameMetrics::START_SCROLL_ID + 1); + + // Make layers[1] the root content + FrameMetrics childMetrics = layers[1]->GetFrameMetrics(0); + childMetrics.SetIsRootContent(true); + layers[1]->SetFrameMetrics(childMetrics); + + // Both layers are fully dispatch-to-content + EventRegions regions; + regions.mHitRegion = nsIntRegion(IntRect(0, 0, 100, 100)); + regions.mDispatchToContentHitRegion = regions.mHitRegion; + layers[0]->SetEventRegions(regions); + layers[1]->SetEventRegions(regions); + } +}; + +#endif // mozilla_layers_APZCTreeManagerTester_h
new file mode 100644 --- /dev/null +++ b/gfx/layers/apz/test/gtest/APZTestCommon.h @@ -0,0 +1,297 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set sw=2 ts=8 et 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_layers_APZTestCommon_h +#define mozilla_layers_APZTestCommon_h + +/** + * Defines a set of mock classes and utility functions/classes for + * writing APZ gtests. + */ + +#include "gtest/gtest.h" +#include "gmock/gmock.h" + +#include "mozilla/Attributes.h" +#include "mozilla/layers/AsyncCompositionManager.h" // for ViewTransform +#include "mozilla/layers/GeckoContentController.h" +#include "mozilla/layers/CompositorParent.h" +#include "mozilla/layers/APZCTreeManager.h" +#include "mozilla/layers/LayerMetricsWrapper.h" +#include "mozilla/layers/APZThreadUtils.h" +#include "mozilla/UniquePtr.h" +#include "apz/src/AsyncPanZoomController.h" +#include "apz/src/HitTestingTreeNode.h" +#include "base/task.h" +#include "Layers.h" +#include "TestLayers.h" +#include "UnitTransforms.h" +#include "gfxPrefs.h" + +using namespace mozilla; +using namespace mozilla::gfx; +using namespace mozilla::layers; +using ::testing::_; +using ::testing::NiceMock; +using ::testing::AtLeast; +using ::testing::AtMost; +using ::testing::MockFunction; +using ::testing::InSequence; + +class Task; + +template<class T> +class ScopedGfxPref { +public: + ScopedGfxPref(T (*aGetPrefFunc)(void), void (*aSetPrefFunc)(T), T aVal) + : mSetPrefFunc(aSetPrefFunc) + { + mOldVal = aGetPrefFunc(); + aSetPrefFunc(aVal); + } + + ~ScopedGfxPref() { + mSetPrefFunc(mOldVal); + } + +private: + void (*mSetPrefFunc)(T); + T mOldVal; +}; + +#define SCOPED_GFX_PREF(prefBase, prefType, prefValue) \ + ScopedGfxPref<prefType> pref_##prefBase( \ + &(gfxPrefs::prefBase), \ + &(gfxPrefs::Set##prefBase), \ + prefValue) + +class MockContentController : public GeckoContentController { +public: + MOCK_METHOD1(RequestContentRepaint, void(const FrameMetrics&)); + MOCK_METHOD2(RequestFlingSnap, void(const FrameMetrics::ViewID& aScrollId, const mozilla::CSSPoint& aDestination)); + MOCK_METHOD2(AcknowledgeScrollUpdate, void(const FrameMetrics::ViewID&, const uint32_t& aScrollGeneration)); + MOCK_METHOD3(HandleDoubleTap, void(const CSSPoint&, Modifiers, const ScrollableLayerGuid&)); + MOCK_METHOD3(HandleSingleTap, void(const CSSPoint&, Modifiers, const ScrollableLayerGuid&)); + MOCK_METHOD4(HandleLongTap, void(const CSSPoint&, Modifiers, const ScrollableLayerGuid&, uint64_t)); + MOCK_METHOD2(PostDelayedTask, void(Task* aTask, int aDelayMs)); + MOCK_METHOD3(NotifyAPZStateChange, void(const ScrollableLayerGuid& aGuid, APZStateChange aChange, int aArg)); + MOCK_METHOD0(NotifyFlushComplete, void()); +}; + +class MockContentControllerDelayed : public MockContentController { +public: + MockContentControllerDelayed() + : mTime(TimeStamp::Now()) + { + } + + const TimeStamp& Time() { + return mTime; + } + + void AdvanceByMillis(int aMillis) { + AdvanceBy(TimeDuration::FromMilliseconds(aMillis)); + } + + void AdvanceBy(const TimeDuration& aIncrement) { + TimeStamp target = mTime + aIncrement; + while (mTaskQueue.Length() > 0 && mTaskQueue[0].second <= target) { + RunNextDelayedTask(); + } + mTime = target; + } + + void PostDelayedTask(Task* aTask, int aDelayMs) { + TimeStamp runAtTime = mTime + TimeDuration::FromMilliseconds(aDelayMs); + int insIndex = mTaskQueue.Length(); + while (insIndex > 0) { + if (mTaskQueue[insIndex - 1].second <= runAtTime) { + break; + } + insIndex--; + } + mTaskQueue.InsertElementAt(insIndex, std::make_pair(aTask, runAtTime)); + } + + // Run all the tasks in the queue, returning the number of tasks + // run. Note that if a task queues another task while running, that + // new task will not be run. Therefore, there may be still be tasks + // in the queue after this function is called. Only when the return + // value is 0 is the queue guaranteed to be empty. + int RunThroughDelayedTasks() { + nsTArray<std::pair<Task*, TimeStamp>> runQueue; + runQueue.SwapElements(mTaskQueue); + int numTasks = runQueue.Length(); + for (int i = 0; i < numTasks; i++) { + mTime = runQueue[i].second; + runQueue[i].first->Run(); + + // Deleting the task is important in order to release the reference to + // the callee object. + delete runQueue[i].first; + } + return numTasks; + } + +private: + void RunNextDelayedTask() { + std::pair<Task*, TimeStamp> next = mTaskQueue[0]; + mTaskQueue.RemoveElementAt(0); + mTime = next.second; + next.first->Run(); + // Deleting the task is important in order to release the reference to + // the callee object. + delete next.first; + } + + // The following array is sorted by timestamp (tasks are inserted in order by + // timestamp). + nsTArray<std::pair<Task*, TimeStamp>> mTaskQueue; + TimeStamp mTime; +}; + +class TestAPZCTreeManager : public APZCTreeManager { +public: + explicit TestAPZCTreeManager(MockContentControllerDelayed* aMcc) : mcc(aMcc) {} + + RefPtr<InputQueue> GetInputQueue() const { + return mInputQueue; + } + +protected: + AsyncPanZoomController* NewAPZCInstance(uint64_t aLayersId, + GeckoContentController* aController) override; + + TimeStamp GetFrameTime() override { + return mcc->Time(); + } + +private: + RefPtr<MockContentControllerDelayed> mcc; +}; + +class TestAsyncPanZoomController : public AsyncPanZoomController { +public: + TestAsyncPanZoomController(uint64_t aLayersId, MockContentControllerDelayed* aMcc, + TestAPZCTreeManager* aTreeManager, + GestureBehavior aBehavior = DEFAULT_GESTURES) + : AsyncPanZoomController(aLayersId, aTreeManager, aTreeManager->GetInputQueue(), + aMcc, aBehavior) + , mWaitForMainThread(false) + , mcc(aMcc) + {} + + nsEventStatus ReceiveInputEvent(const InputData& aEvent, ScrollableLayerGuid* aDummy, uint64_t* aOutInputBlockId) { + // This is a function whose signature matches exactly the ReceiveInputEvent + // on APZCTreeManager. This allows us to templates for functions like + // TouchDown, TouchUp, etc so that we can reuse the code for dispatching + // events into both APZC and APZCTM. + return ReceiveInputEvent(aEvent, aOutInputBlockId); + } + + nsEventStatus ReceiveInputEvent(const InputData& aEvent, uint64_t* aOutInputBlockId) { + return GetInputQueue()->ReceiveInputEvent(this, !mWaitForMainThread, aEvent, aOutInputBlockId); + } + + void ContentReceivedInputBlock(uint64_t aInputBlockId, bool aPreventDefault) { + GetInputQueue()->ContentReceivedInputBlock(aInputBlockId, aPreventDefault); + } + + void ConfirmTarget(uint64_t aInputBlockId) { + RefPtr<AsyncPanZoomController> target = this; + GetInputQueue()->SetConfirmedTargetApzc(aInputBlockId, target); + } + + void SetAllowedTouchBehavior(uint64_t aInputBlockId, const nsTArray<TouchBehaviorFlags>& aBehaviors) { + GetInputQueue()->SetAllowedTouchBehavior(aInputBlockId, aBehaviors); + } + + void SetFrameMetrics(const FrameMetrics& metrics) { + ReentrantMonitorAutoEnter lock(mMonitor); + mFrameMetrics = metrics; + } + + FrameMetrics& GetFrameMetrics() { + ReentrantMonitorAutoEnter lock(mMonitor); + return mFrameMetrics; + } + + const FrameMetrics& GetFrameMetrics() const { + ReentrantMonitorAutoEnter lock(mMonitor); + return mFrameMetrics; + } + + using AsyncPanZoomController::GetVelocityVector; + + void AssertStateIsReset() const { + ReentrantMonitorAutoEnter lock(mMonitor); + EXPECT_EQ(NOTHING, mState); + } + + void AssertStateIsFling() const { + ReentrantMonitorAutoEnter lock(mMonitor); + EXPECT_EQ(FLING, mState); + } + + void AdvanceAnimationsUntilEnd(const TimeDuration& aIncrement = TimeDuration::FromMilliseconds(10)) { + while (AdvanceAnimations(mcc->Time())) { + mcc->AdvanceBy(aIncrement); + } + } + + bool SampleContentTransformForFrame(AsyncTransform* aOutTransform, + ParentLayerPoint& aScrollOffset, + const TimeDuration& aIncrement = TimeDuration::FromMilliseconds(0)) { + mcc->AdvanceBy(aIncrement); + bool ret = AdvanceAnimations(mcc->Time()); + AsyncPanZoomController::SampleContentTransformForFrame( + aOutTransform, aScrollOffset); + return ret; + } + + void SetWaitForMainThread() { + mWaitForMainThread = true; + } + + static TimeStamp GetStartupTime() { + static TimeStamp sStartupTime = TimeStamp::Now(); + return sStartupTime; + } + +private: + bool mWaitForMainThread; + MockContentControllerDelayed* mcc; +}; + +AsyncPanZoomController* +TestAPZCTreeManager::NewAPZCInstance(uint64_t aLayersId, + GeckoContentController* aController) +{ + MockContentControllerDelayed* mcc = static_cast<MockContentControllerDelayed*>(aController); + return new TestAsyncPanZoomController(aLayersId, mcc, this, + AsyncPanZoomController::USE_GESTURE_DETECTOR); +} + +FrameMetrics +TestFrameMetrics() +{ + FrameMetrics fm; + + fm.SetDisplayPort(CSSRect(0, 0, 10, 10)); + fm.SetCompositionBounds(ParentLayerRect(0, 0, 10, 10)); + fm.SetCriticalDisplayPort(CSSRect(0, 0, 10, 10)); + fm.SetScrollableRect(CSSRect(0, 0, 100, 100)); + + return fm; +} + +uint32_t +MillisecondsSinceStartup(TimeStamp aTime) +{ + return (aTime - TestAsyncPanZoomController::GetStartupTime()).ToMilliseconds(); +} + +#endif // mozilla_layers_APZTestCommon_h
new file mode 100644 --- /dev/null +++ b/gfx/layers/apz/test/gtest/InputUtils.h @@ -0,0 +1,466 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set sw=2 ts=8 et 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_layers_InputUtils_h +#define mozilla_layers_InputUtils_h + +/** + * Defines a set of utility functions for generating input events + * to an APZC/APZCTM during APZ gtests. + */ + +#include "APZTestCommon.h" + +/* The InputReceiver template parameter used in the helper functions below needs + * to be a class that implements functions with the signatures: + * nsEventStatus ReceiveInputEvent(const InputData& aEvent, + * ScrollableLayerGuid* aGuid, + * uint64_t* aOutInputBlockId); + * void SetAllowedTouchBehavior(uint64_t aInputBlockId, + * const nsTArray<uint32_t>& aBehaviours); + * The classes that currently implement these are APZCTreeManager and + * TestAsyncPanZoomController. Using this template allows us to test individual + * APZC instances in isolation and also an entire APZ tree, while using the same + * code to dispatch input events. + */ + +// Some helper functions for constructing input event objects suitable to be +// passed either to an APZC (which expects an transformed point), or to an APZTM +// (which expects an untransformed point). We handle both cases by setting both +// the transformed and untransformed fields to the same value. +SingleTouchData +CreateSingleTouchData(int32_t aIdentifier, int aX, int aY) +{ + SingleTouchData touch(aIdentifier, ScreenIntPoint(aX, aY), ScreenSize(0, 0), 0, 0); + touch.mLocalScreenPoint = ParentLayerPoint(aX, aY); + return touch; +} + +PinchGestureInput +CreatePinchGestureInput(PinchGestureInput::PinchGestureType aType, + int aFocusX, int aFocusY, + float aCurrentSpan, float aPreviousSpan) +{ + PinchGestureInput result(aType, 0, TimeStamp(), ScreenPoint(aFocusX, aFocusY), + aCurrentSpan, aPreviousSpan, 0); + result.mLocalFocusPoint = ParentLayerPoint(aFocusX, aFocusY); + return result; +} + +template<class InputReceiver> +void +SetDefaultAllowedTouchBehavior(const RefPtr<InputReceiver>& aTarget, + uint64_t aInputBlockId, + int touchPoints = 1) +{ + nsTArray<uint32_t> defaultBehaviors; + // use the default value where everything is allowed + for (int i = 0; i < touchPoints; i++) { + defaultBehaviors.AppendElement(mozilla::layers::AllowedTouchBehavior::HORIZONTAL_PAN + | mozilla::layers::AllowedTouchBehavior::VERTICAL_PAN + | mozilla::layers::AllowedTouchBehavior::PINCH_ZOOM + | mozilla::layers::AllowedTouchBehavior::DOUBLE_TAP_ZOOM); + } + aTarget->SetAllowedTouchBehavior(aInputBlockId, defaultBehaviors); +} + + +MultiTouchInput +CreateMultiTouchInput(MultiTouchInput::MultiTouchType aType, TimeStamp aTime) +{ + return MultiTouchInput(aType, MillisecondsSinceStartup(aTime), aTime, 0); +} + +template<class InputReceiver> +nsEventStatus +TouchDown(const RefPtr<InputReceiver>& aTarget, int aX, int aY, TimeStamp aTime, uint64_t* aOutInputBlockId = nullptr) +{ + MultiTouchInput mti = CreateMultiTouchInput(MultiTouchInput::MULTITOUCH_START, aTime); + mti.mTouches.AppendElement(CreateSingleTouchData(0, aX, aY)); + return aTarget->ReceiveInputEvent(mti, nullptr, aOutInputBlockId); +} + +template<class InputReceiver> +nsEventStatus +TouchMove(const RefPtr<InputReceiver>& aTarget, int aX, int aY, TimeStamp aTime) +{ + MultiTouchInput mti = CreateMultiTouchInput(MultiTouchInput::MULTITOUCH_MOVE, aTime); + mti.mTouches.AppendElement(CreateSingleTouchData(0, aX, aY)); + return aTarget->ReceiveInputEvent(mti, nullptr, nullptr); +} + +template<class InputReceiver> +nsEventStatus +TouchUp(const RefPtr<InputReceiver>& aTarget, int aX, int aY, TimeStamp aTime) +{ + MultiTouchInput mti = CreateMultiTouchInput(MultiTouchInput::MULTITOUCH_END, aTime); + mti.mTouches.AppendElement(CreateSingleTouchData(0, aX, aY)); + return aTarget->ReceiveInputEvent(mti, nullptr, nullptr); +} + +template<class InputReceiver> +void +Tap(const RefPtr<InputReceiver>& aTarget, int aX, int aY, MockContentControllerDelayed* aMcc, + TimeDuration aTapLength, + nsEventStatus (*aOutEventStatuses)[2] = nullptr, + uint64_t* aOutInputBlockId = nullptr) +{ + // Even if the caller doesn't care about the block id, we need it to set the + // allowed touch behaviour below, so make sure aOutInputBlockId is non-null. + uint64_t blockId; + if (!aOutInputBlockId) { + aOutInputBlockId = &blockId; + } + + nsEventStatus status = TouchDown(aTarget, aX, aY, aMcc->Time(), aOutInputBlockId); + if (aOutEventStatuses) { + (*aOutEventStatuses)[0] = status; + } + aMcc->AdvanceBy(aTapLength); + + // If touch-action is enabled then simulate the allowed touch behaviour + // notification that the main thread is supposed to deliver. + if (gfxPrefs::TouchActionEnabled() && status != nsEventStatus_eConsumeNoDefault) { + SetDefaultAllowedTouchBehavior(aTarget, *aOutInputBlockId); + } + + status = TouchUp(aTarget, aX, aY, aMcc->Time()); + if (aOutEventStatuses) { + (*aOutEventStatuses)[1] = status; + } +} + +template<class InputReceiver> +void +TapAndCheckStatus(const RefPtr<InputReceiver>& aTarget, int aX, int aY, + MockContentControllerDelayed* aMcc, TimeDuration aTapLength) +{ + nsEventStatus statuses[2]; + Tap(aTarget, aX, aY, aMcc, aTapLength, &statuses); + EXPECT_EQ(nsEventStatus_eConsumeDoDefault, statuses[0]); + EXPECT_EQ(nsEventStatus_eConsumeDoDefault, statuses[1]); +} + +template<class InputReceiver> +void +Pan(const RefPtr<InputReceiver>& aTarget, + MockContentControllerDelayed* aMcc, + const ScreenPoint& aTouchStart, + const ScreenPoint& aTouchEnd, + bool aKeepFingerDown = false, + nsTArray<uint32_t>* aAllowedTouchBehaviors = nullptr, + nsEventStatus (*aOutEventStatuses)[4] = nullptr, + uint64_t* aOutInputBlockId = nullptr) +{ + // Reduce the touch start and move tolerance to a tiny value. + // We can't use a scoped pref because this value might be read at some later + // time when the events are actually processed, rather than when we deliver + // them. + gfxPrefs::SetAPZTouchStartTolerance(1.0f / 1000.0f); + gfxPrefs::SetAPZTouchMoveTolerance(0.0f); + const int OVERCOME_TOUCH_TOLERANCE = 1; + + const TimeDuration TIME_BETWEEN_TOUCH_EVENT = TimeDuration::FromMilliseconds(50); + + // Even if the caller doesn't care about the block id, we need it to set the + // allowed touch behaviour below, so make sure aOutInputBlockId is non-null. + uint64_t blockId; + if (!aOutInputBlockId) { + aOutInputBlockId = &blockId; + } + + // Make sure the move is large enough to not be handled as a tap + nsEventStatus status = TouchDown(aTarget, aTouchStart.x, aTouchStart.y + OVERCOME_TOUCH_TOLERANCE, aMcc->Time(), aOutInputBlockId); + if (aOutEventStatuses) { + (*aOutEventStatuses)[0] = status; + } + + aMcc->AdvanceBy(TIME_BETWEEN_TOUCH_EVENT); + + // Allowed touch behaviours must be set after sending touch-start. + if (status != nsEventStatus_eConsumeNoDefault) { + if (aAllowedTouchBehaviors) { + EXPECT_EQ(1UL, aAllowedTouchBehaviors->Length()); + aTarget->SetAllowedTouchBehavior(*aOutInputBlockId, *aAllowedTouchBehaviors); + } else if (gfxPrefs::TouchActionEnabled()) { + SetDefaultAllowedTouchBehavior(aTarget, *aOutInputBlockId); + } + } + + status = TouchMove(aTarget, aTouchStart.x, aTouchStart.y, aMcc->Time()); + if (aOutEventStatuses) { + (*aOutEventStatuses)[1] = status; + } + + aMcc->AdvanceBy(TIME_BETWEEN_TOUCH_EVENT); + + status = TouchMove(aTarget, aTouchEnd.x, aTouchEnd.y, aMcc->Time()); + if (aOutEventStatuses) { + (*aOutEventStatuses)[2] = status; + } + + aMcc->AdvanceBy(TIME_BETWEEN_TOUCH_EVENT); + + if (!aKeepFingerDown) { + status = TouchUp(aTarget, aTouchEnd.x, aTouchEnd.y, aMcc->Time()); + } else { + status = nsEventStatus_eIgnore; + } + if (aOutEventStatuses) { + (*aOutEventStatuses)[3] = status; + } + + // Don't increment the time here. Animations started on touch-up, such as + // flings, are affected by elapsed time, and we want to be able to sample + // them immediately after they start, without time having elapsed. +} + +// A version of Pan() that only takes y coordinates rather than (x, y) points +// for the touch start and end points, and uses 10 for the x coordinates. +// This is for convenience, as most tests only need to pan in one direction. +template<class InputReceiver> +void +Pan(const RefPtr<InputReceiver>& aTarget, + MockContentControllerDelayed* aMcc, + int aTouchStartY, + int aTouchEndY, + bool aKeepFingerDown = false, + nsTArray<uint32_t>* aAllowedTouchBehaviors = nullptr, + nsEventStatus (*aOutEventStatuses)[4] = nullptr, + uint64_t* aOutInputBlockId = nullptr) +{ + ::Pan(aTarget, aMcc, ScreenPoint(10, aTouchStartY), ScreenPoint(10, aTouchEndY), + aKeepFingerDown, aAllowedTouchBehaviors, aOutEventStatuses, aOutInputBlockId); +} + +/* + * Dispatches mock touch events to the apzc and checks whether apzc properly + * consumed them and triggered scrolling behavior. + */ +template<class InputReceiver> +void +PanAndCheckStatus(const RefPtr<InputReceiver>& aTarget, + MockContentControllerDelayed* aMcc, + int aTouchStartY, + int aTouchEndY, + bool aExpectConsumed, + nsTArray<uint32_t>* aAllowedTouchBehaviors, + uint64_t* aOutInputBlockId = nullptr) +{ + nsEventStatus statuses[4]; // down, move, move, up + Pan(aTarget, aMcc, aTouchStartY, aTouchEndY, false, aAllowedTouchBehaviors, &statuses, aOutInputBlockId); + + EXPECT_EQ(nsEventStatus_eConsumeDoDefault, statuses[0]); + + nsEventStatus touchMoveStatus; + if (aExpectConsumed) { + touchMoveStatus = nsEventStatus_eConsumeDoDefault; + } else { + touchMoveStatus = nsEventStatus_eIgnore; + } + EXPECT_EQ(touchMoveStatus, statuses[1]); + EXPECT_EQ(touchMoveStatus, statuses[2]); +} + +void +ApzcPanNoFling(const RefPtr<TestAsyncPanZoomController>& aApzc, + MockContentControllerDelayed* aMcc, + int aTouchStartY, + int aTouchEndY, + uint64_t* aOutInputBlockId = nullptr) +{ + Pan(aApzc, aMcc, aTouchStartY, aTouchEndY, false, nullptr, nullptr, aOutInputBlockId); + aApzc->CancelAnimation(); +} + +template<class InputReceiver> +void +PinchWithPinchInput(const RefPtr<InputReceiver>& aTarget, + int aFocusX, int aFocusY, int aSecondFocusX, int aSecondFocusY, float aScale, + nsEventStatus (*aOutEventStatuses)[3] = nullptr) +{ + nsEventStatus actualStatus = aTarget->ReceiveInputEvent( + CreatePinchGestureInput(PinchGestureInput::PINCHGESTURE_START, + aFocusX, aFocusY, 10.0, 10.0), + nullptr); + if (aOutEventStatuses) { + (*aOutEventStatuses)[0] = actualStatus; + } + actualStatus = aTarget->ReceiveInputEvent( + CreatePinchGestureInput(PinchGestureInput::PINCHGESTURE_SCALE, + aSecondFocusX, aSecondFocusY, 10.0 * aScale, 10.0), + nullptr); + if (aOutEventStatuses) { + (*aOutEventStatuses)[1] = actualStatus; + } + actualStatus = aTarget->ReceiveInputEvent( + CreatePinchGestureInput(PinchGestureInput::PINCHGESTURE_END, + // note: negative values here tell APZC + // not to turn the pinch into a pan + aFocusX, aFocusY, -1.0, -1.0), + nullptr); + if (aOutEventStatuses) { + (*aOutEventStatuses)[2] = actualStatus; + } +} + +template<class InputReceiver> +void +PinchWithPinchInputAndCheckStatus(const RefPtr<InputReceiver>& aTarget, + int aFocusX, int aFocusY, float aScale, + bool aShouldTriggerPinch) +{ + nsEventStatus statuses[3]; // scalebegin, scale, scaleend + PinchWithPinchInput(aTarget, aFocusX, aFocusY, aFocusX, aFocusY, aScale, &statuses); + + nsEventStatus expectedStatus = aShouldTriggerPinch + ? nsEventStatus_eConsumeNoDefault + : nsEventStatus_eIgnore; + EXPECT_EQ(expectedStatus, statuses[0]); + EXPECT_EQ(expectedStatus, statuses[1]); +} + +template<class InputReceiver> +void +PinchWithTouchInput(const RefPtr<InputReceiver>& aTarget, + int aFocusX, int aFocusY, float aScale, + int& inputId, + nsTArray<uint32_t>* aAllowedTouchBehaviors = nullptr, + nsEventStatus (*aOutEventStatuses)[4] = nullptr, + uint64_t* aOutInputBlockId = nullptr) +{ + // Having pinch coordinates in float type may cause problems with high-precision scale values + // since SingleTouchData accepts integer value. But for trivial tests it should be ok. + float pinchLength = 100.0; + float pinchLengthScaled = pinchLength * aScale; + + // Even if the caller doesn't care about the block id, we need it to set the + // allowed touch behaviour below, so make sure aOutInputBlockId is non-null. + uint64_t blockId; + if (!aOutInputBlockId) { + aOutInputBlockId = &blockId; + } + + MultiTouchInput mtiStart = MultiTouchInput(MultiTouchInput::MULTITOUCH_START, 0, TimeStamp(), 0); + mtiStart.mTouches.AppendElement(CreateSingleTouchData(inputId, aFocusX, aFocusY)); + mtiStart.mTouches.AppendElement(CreateSingleTouchData(inputId + 1, aFocusX, aFocusY)); + nsEventStatus status = aTarget->ReceiveInputEvent(mtiStart, aOutInputBlockId); + if (aOutEventStatuses) { + (*aOutEventStatuses)[0] = status; + } + + if (aAllowedTouchBehaviors) { + EXPECT_EQ(2UL, aAllowedTouchBehaviors->Length()); + aTarget->SetAllowedTouchBehavior(*aOutInputBlockId, *aAllowedTouchBehaviors); + } else if (gfxPrefs::TouchActionEnabled()) { + SetDefaultAllowedTouchBehavior(aTarget, *aOutInputBlockId, 2); + } + + MultiTouchInput mtiMove1 = MultiTouchInput(MultiTouchInput::MULTITOUCH_MOVE, 0, TimeStamp(), 0); + mtiMove1.mTouches.AppendElement(CreateSingleTouchData(inputId, aFocusX - pinchLength, aFocusY)); + mtiMove1.mTouches.AppendElement(CreateSingleTouchData(inputId + 1, aFocusX + pinchLength, aFocusY)); + status = aTarget->ReceiveInputEvent(mtiMove1, nullptr); + if (aOutEventStatuses) { + (*aOutEventStatuses)[1] = status; + } + + MultiTouchInput mtiMove2 = MultiTouchInput(MultiTouchInput::MULTITOUCH_MOVE, 0, TimeStamp(), 0); + mtiMove2.mTouches.AppendElement(CreateSingleTouchData(inputId, aFocusX - pinchLengthScaled, aFocusY)); + mtiMove2.mTouches.AppendElement(CreateSingleTouchData(inputId + 1, aFocusX + pinchLengthScaled, aFocusY)); + status = aTarget->ReceiveInputEvent(mtiMove2, nullptr); + if (aOutEventStatuses) { + (*aOutEventStatuses)[2] = status; + } + + MultiTouchInput mtiEnd = MultiTouchInput(MultiTouchInput::MULTITOUCH_END, 0, TimeStamp(), 0); + mtiEnd.mTouches.AppendElement(CreateSingleTouchData(inputId, aFocusX - pinchLengthScaled, aFocusY)); + mtiEnd.mTouches.AppendElement(CreateSingleTouchData(inputId + 1, aFocusX + pinchLengthScaled, aFocusY)); + status = aTarget->ReceiveInputEvent(mtiEnd, nullptr); + if (aOutEventStatuses) { + (*aOutEventStatuses)[3] = status; + } + + inputId += 2; +} + +template<class InputReceiver> +void +PinchWithTouchInputAndCheckStatus(const RefPtr<InputReceiver>& aTarget, + int aFocusX, int aFocusY, float aScale, + int& inputId, bool aShouldTriggerPinch, + nsTArray<uint32_t>* aAllowedTouchBehaviors) +{ + nsEventStatus statuses[4]; // down, move, move, up + PinchWithTouchInput(aTarget, aFocusX, aFocusY, aScale, inputId, aAllowedTouchBehaviors, &statuses); + + nsEventStatus expectedMoveStatus = aShouldTriggerPinch + ? nsEventStatus_eConsumeDoDefault + : nsEventStatus_eIgnore; + EXPECT_EQ(nsEventStatus_eConsumeDoDefault, statuses[0]); + EXPECT_EQ(expectedMoveStatus, statuses[1]); + EXPECT_EQ(expectedMoveStatus, statuses[2]); +} + +template<class InputReceiver> +void +DoubleTap(const RefPtr<InputReceiver>& aTarget, int aX, int aY, MockContentControllerDelayed* aMcc, + nsEventStatus (*aOutEventStatuses)[4] = nullptr, + uint64_t (*aOutInputBlockIds)[2] = nullptr) +{ + uint64_t blockId; + nsEventStatus status = TouchDown(aTarget, aX, aY, aMcc->Time(), &blockId); + if (aOutEventStatuses) { + (*aOutEventStatuses)[0] = status; + } + if (aOutInputBlockIds) { + (*aOutInputBlockIds)[0] = blockId; + } + aMcc->AdvanceByMillis(10); + + // If touch-action is enabled then simulate the allowed touch behaviour + // notification that the main thread is supposed to deliver. + if (gfxPrefs::TouchActionEnabled() && status != nsEventStatus_eConsumeNoDefault) { + SetDefaultAllowedTouchBehavior(aTarget, blockId); + } + + status = TouchUp(aTarget, aX, aY, aMcc->Time()); + if (aOutEventStatuses) { + (*aOutEventStatuses)[1] = status; + } + aMcc->AdvanceByMillis(10); + status = TouchDown(aTarget, aX, aY, aMcc->Time(), &blockId); + if (aOutEventStatuses) { + (*aOutEventStatuses)[2] = status; + } + if (aOutInputBlockIds) { + (*aOutInputBlockIds)[1] = blockId; + } + aMcc->AdvanceByMillis(10); + + if (gfxPrefs::TouchActionEnabled() && status != nsEventStatus_eConsumeNoDefault) { + SetDefaultAllowedTouchBehavior(aTarget, blockId); + } + + status = TouchUp(aTarget, aX, aY, aMcc->Time()); + if (aOutEventStatuses) { + (*aOutEventStatuses)[3] = status; + } +} + +template<class InputReceiver> +void +DoubleTapAndCheckStatus(const RefPtr<InputReceiver>& aTarget, int aX, int aY, + MockContentControllerDelayed* aMcc, uint64_t (*aOutInputBlockIds)[2] = nullptr) +{ + nsEventStatus statuses[4]; + DoubleTap(aTarget, aX, aY, aMcc, &statuses, aOutInputBlockIds); + EXPECT_EQ(nsEventStatus_eConsumeDoDefault, statuses[0]); + EXPECT_EQ(nsEventStatus_eConsumeDoDefault, statuses[1]); + EXPECT_EQ(nsEventStatus_eConsumeDoDefault, statuses[2]); + EXPECT_EQ(nsEventStatus_eConsumeDoDefault, statuses[3]); +} + +#endif // mozilla_layers_InputUtils_h
deleted file mode 100644 --- a/gfx/layers/apz/test/gtest/TestAsyncPanZoomController.cpp +++ /dev/null @@ -1,3489 +0,0 @@ -/* vim:set ts=2 sw=2 sts=2 et: */ -/* Any copyright is dedicated to the Public Domain. - * http://creativecommons.org/publicdomain/zero/1.0/ - */ - -#include "gtest/gtest.h" -#include "gmock/gmock.h" - -#include "mozilla/Attributes.h" -#include "mozilla/layers/AsyncCompositionManager.h" // for ViewTransform -#include "mozilla/layers/GeckoContentController.h" -#include "mozilla/layers/CompositorParent.h" -#include "mozilla/layers/APZCTreeManager.h" -#include "mozilla/layers/LayerMetricsWrapper.h" -#include "mozilla/layers/APZThreadUtils.h" -#include "mozilla/UniquePtr.h" -#include "apz/src/AsyncPanZoomController.h" -#include "apz/src/HitTestingTreeNode.h" -#include "base/task.h" -#include "Layers.h" -#include "TestLayers.h" -#include "UnitTransforms.h" -#include "gfxPrefs.h" - -using namespace mozilla; -using namespace mozilla::gfx; -using namespace mozilla::layers; -using ::testing::_; -using ::testing::NiceMock; -using ::testing::AtLeast; -using ::testing::AtMost; -using ::testing::MockFunction; -using ::testing::InSequence; - -class Task; - -template<class T> -class ScopedGfxPref { -public: - ScopedGfxPref(T (*aGetPrefFunc)(void), void (*aSetPrefFunc)(T), T aVal) - : mSetPrefFunc(aSetPrefFunc) - { - mOldVal = aGetPrefFunc(); - aSetPrefFunc(aVal); - } - - ~ScopedGfxPref() { - mSetPrefFunc(mOldVal); - } - -private: - void (*mSetPrefFunc)(T); - T mOldVal; -}; - -#define SCOPED_GFX_PREF(prefBase, prefType, prefValue) \ - ScopedGfxPref<prefType> pref_##prefBase( \ - &(gfxPrefs::prefBase), \ - &(gfxPrefs::Set##prefBase), \ - prefValue) - -class MockContentController : public GeckoContentController { -public: - MOCK_METHOD1(RequestContentRepaint, void(const FrameMetrics&)); - MOCK_METHOD2(RequestFlingSnap, void(const FrameMetrics::ViewID& aScrollId, const mozilla::CSSPoint& aDestination)); - MOCK_METHOD2(AcknowledgeScrollUpdate, void(const FrameMetrics::ViewID&, const uint32_t& aScrollGeneration)); - MOCK_METHOD3(HandleDoubleTap, void(const CSSPoint&, Modifiers, const ScrollableLayerGuid&)); - MOCK_METHOD3(HandleSingleTap, void(const CSSPoint&, Modifiers, const ScrollableLayerGuid&)); - MOCK_METHOD4(HandleLongTap, void(const CSSPoint&, Modifiers, const ScrollableLayerGuid&, uint64_t)); - MOCK_METHOD2(PostDelayedTask, void(Task* aTask, int aDelayMs)); - MOCK_METHOD3(NotifyAPZStateChange, void(const ScrollableLayerGuid& aGuid, APZStateChange aChange, int aArg)); - MOCK_METHOD0(NotifyFlushComplete, void()); -}; - -class MockContentControllerDelayed : public MockContentController { -public: - MockContentControllerDelayed() - : mTime(TimeStamp::Now()) - { - } - - const TimeStamp& Time() { - return mTime; - } - - void AdvanceByMillis(int aMillis) { - AdvanceBy(TimeDuration::FromMilliseconds(aMillis)); - } - - void AdvanceBy(const TimeDuration& aIncrement) { - TimeStamp target = mTime + aIncrement; - while (mTaskQueue.Length() > 0 && mTaskQueue[0].second <= target) { - RunNextDelayedTask(); - } - mTime = target; - } - - void PostDelayedTask(Task* aTask, int aDelayMs) { - TimeStamp runAtTime = mTime + TimeDuration::FromMilliseconds(aDelayMs); - int insIndex = mTaskQueue.Length(); - while (insIndex > 0) { - if (mTaskQueue[insIndex - 1].second <= runAtTime) { - break; - } - insIndex--; - } - mTaskQueue.InsertElementAt(insIndex, std::make_pair(aTask, runAtTime)); - } - - // Run all the tasks in the queue, returning the number of tasks - // run. Note that if a task queues another task while running, that - // new task will not be run. Therefore, there may be still be tasks - // in the queue after this function is called. Only when the return - // value is 0 is the queue guaranteed to be empty. - int RunThroughDelayedTasks() { - nsTArray<std::pair<Task*, TimeStamp>> runQueue; - runQueue.SwapElements(mTaskQueue); - int numTasks = runQueue.Length(); - for (int i = 0; i < numTasks; i++) { - mTime = runQueue[i].second; - runQueue[i].first->Run(); - - // Deleting the task is important in order to release the reference to - // the callee object. - delete runQueue[i].first; - } - return numTasks; - } - -private: - void RunNextDelayedTask() { - std::pair<Task*, TimeStamp> next = mTaskQueue[0]; - mTaskQueue.RemoveElementAt(0); - mTime = next.second; - next.first->Run(); - // Deleting the task is important in order to release the reference to - // the callee object. - delete next.first; - } - - // The following array is sorted by timestamp (tasks are inserted in order by - // timestamp). - nsTArray<std::pair<Task*, TimeStamp>> mTaskQueue; - TimeStamp mTime; -}; - -class TestAPZCTreeManager : public APZCTreeManager { -public: - explicit TestAPZCTreeManager(MockContentControllerDelayed* aMcc) : mcc(aMcc) {} - - RefPtr<InputQueue> GetInputQueue() const { - return mInputQueue; - } - -protected: - AsyncPanZoomController* NewAPZCInstance(uint64_t aLayersId, - GeckoContentController* aController) override; - - TimeStamp GetFrameTime() override { - return mcc->Time(); - } - -private: - RefPtr<MockContentControllerDelayed> mcc; -}; - -class TestAsyncPanZoomController : public AsyncPanZoomController { -public: - TestAsyncPanZoomController(uint64_t aLayersId, MockContentControllerDelayed* aMcc, - TestAPZCTreeManager* aTreeManager, - GestureBehavior aBehavior = DEFAULT_GESTURES) - : AsyncPanZoomController(aLayersId, aTreeManager, aTreeManager->GetInputQueue(), - aMcc, aBehavior) - , mWaitForMainThread(false) - , mcc(aMcc) - {} - - nsEventStatus ReceiveInputEvent(const InputData& aEvent, ScrollableLayerGuid* aDummy, uint64_t* aOutInputBlockId) { - // This is a function whose signature matches exactly the ReceiveInputEvent - // on APZCTreeManager. This allows us to templates for functions like - // TouchDown, TouchUp, etc so that we can reuse the code for dispatching - // events into both APZC and APZCTM. - return ReceiveInputEvent(aEvent, aOutInputBlockId); - } - - nsEventStatus ReceiveInputEvent(const InputData& aEvent, uint64_t* aOutInputBlockId) { - return GetInputQueue()->ReceiveInputEvent(this, !mWaitForMainThread, aEvent, aOutInputBlockId); - } - - void ContentReceivedInputBlock(uint64_t aInputBlockId, bool aPreventDefault) { - GetInputQueue()->ContentReceivedInputBlock(aInputBlockId, aPreventDefault); - } - - void ConfirmTarget(uint64_t aInputBlockId) { - RefPtr<AsyncPanZoomController> target = this; - GetInputQueue()->SetConfirmedTargetApzc(aInputBlockId, target); - } - - void SetAllowedTouchBehavior(uint64_t aInputBlockId, const nsTArray<TouchBehaviorFlags>& aBehaviors) { - GetInputQueue()->SetAllowedTouchBehavior(aInputBlockId, aBehaviors); - } - - void SetFrameMetrics(const FrameMetrics& metrics) { - ReentrantMonitorAutoEnter lock(mMonitor); - mFrameMetrics = metrics; - } - - FrameMetrics& GetFrameMetrics() { - ReentrantMonitorAutoEnter lock(mMonitor); - return mFrameMetrics; - } - - const FrameMetrics& GetFrameMetrics() const { - ReentrantMonitorAutoEnter lock(mMonitor); - return mFrameMetrics; - } - - using AsyncPanZoomController::GetVelocityVector; - - void AssertStateIsReset() const { - ReentrantMonitorAutoEnter lock(mMonitor); - EXPECT_EQ(NOTHING, mState); - } - - void AssertStateIsFling() const { - ReentrantMonitorAutoEnter lock(mMonitor); - EXPECT_EQ(FLING, mState); - } - - void AdvanceAnimationsUntilEnd(const TimeDuration& aIncrement = TimeDuration::FromMilliseconds(10)) { - while (AdvanceAnimations(mcc->Time())) { - mcc->AdvanceBy(aIncrement); - } - } - - bool SampleContentTransformForFrame(AsyncTransform* aOutTransform, - ParentLayerPoint& aScrollOffset, - const TimeDuration& aIncrement = TimeDuration::FromMilliseconds(0)) { - mcc->AdvanceBy(aIncrement); - bool ret = AdvanceAnimations(mcc->Time()); - AsyncPanZoomController::SampleContentTransformForFrame( - aOutTransform, aScrollOffset); - return ret; - } - - void SetWaitForMainThread() { - mWaitForMainThread = true; - } - - static TimeStamp GetStartupTime() { - static TimeStamp sStartupTime = TimeStamp::Now(); - return sStartupTime; - } - -private: - bool mWaitForMainThread; - MockContentControllerDelayed* mcc; -}; - -AsyncPanZoomController* -TestAPZCTreeManager::NewAPZCInstance(uint64_t aLayersId, - GeckoContentController* aController) -{ - MockContentControllerDelayed* mcc = static_cast<MockContentControllerDelayed*>(aController); - return new TestAsyncPanZoomController(aLayersId, mcc, this, - AsyncPanZoomController::USE_GESTURE_DETECTOR); -} - -static FrameMetrics -TestFrameMetrics() -{ - FrameMetrics fm; - - fm.SetDisplayPort(CSSRect(0, 0, 10, 10)); - fm.SetCompositionBounds(ParentLayerRect(0, 0, 10, 10)); - fm.SetCriticalDisplayPort(CSSRect(0, 0, 10, 10)); - fm.SetScrollableRect(CSSRect(0, 0, 100, 100)); - - return fm; -} - -class APZCBasicTester : public ::testing::Test { -public: - explicit APZCBasicTester(AsyncPanZoomController::GestureBehavior aGestureBehavior = AsyncPanZoomController::DEFAULT_GESTURES) - : mGestureBehavior(aGestureBehavior) - { - } - -protected: - virtual void SetUp() - { - gfxPrefs::GetSingleton(); - APZThreadUtils::SetThreadAssertionsEnabled(false); - APZThreadUtils::SetControllerThread(MessageLoop::current()); - - mcc = new NiceMock<MockContentControllerDelayed>(); - tm = new TestAPZCTreeManager(mcc); - apzc = new TestAsyncPanZoomController(0, mcc, tm, mGestureBehavior); - apzc->SetFrameMetrics(TestFrameMetrics()); - } - - /** - * Get the APZC's scroll range in CSS pixels. - */ - CSSRect GetScrollRange() const - { - const FrameMetrics& metrics = apzc->GetFrameMetrics(); - return CSSRect( - metrics.GetScrollableRect().TopLeft(), - metrics.GetScrollableRect().Size() - metrics.CalculateCompositedSizeInCssPixels()); - } - - virtual void TearDown() - { - while (mcc->RunThroughDelayedTasks()); - apzc->Destroy(); - } - - void MakeApzcWaitForMainThread() - { - apzc->SetWaitForMainThread(); - } - - void MakeApzcZoomable() - { - apzc->UpdateZoomConstraints(ZoomConstraints(true, true, CSSToParentLayerScale(0.25f), CSSToParentLayerScale(4.0f))); - } - - void MakeApzcUnzoomable() - { - apzc->UpdateZoomConstraints(ZoomConstraints(false, false, CSSToParentLayerScale(1.0f), CSSToParentLayerScale(1.0f))); - } - - void PanIntoOverscroll(); - - /** - * Sample animations once, 1 ms later than the last sample. - */ - void SampleAnimationOnce() - { - const TimeDuration increment = TimeDuration::FromMilliseconds(1); - ParentLayerPoint pointOut; - AsyncTransform viewTransformOut; - mcc->AdvanceBy(increment); - apzc->SampleContentTransformForFrame(&viewTransformOut, pointOut); - } - - /** - * Sample animations until we recover from overscroll. - * @param aExpectedScrollOffset the expected reported scroll offset - * throughout the animation - */ - void SampleAnimationUntilRecoveredFromOverscroll(const ParentLayerPoint& aExpectedScrollOffset) - { - const TimeDuration increment = TimeDuration::FromMilliseconds(1); - bool recoveredFromOverscroll = false; - ParentLayerPoint pointOut; - AsyncTransform viewTransformOut; - while (apzc->SampleContentTransformForFrame(&viewTransformOut, pointOut)) { - // The reported scroll offset should be the same throughout. - EXPECT_EQ(aExpectedScrollOffset, pointOut); - - // Trigger computation of the overscroll tranform, to make sure - // no assetions fire during the calculation. - apzc->GetOverscrollTransform(); - - if (!apzc->IsOverscrolled()) { - recoveredFromOverscroll = true; - } - - mcc->AdvanceBy(increment); - } - EXPECT_TRUE(recoveredFromOverscroll); - apzc->AssertStateIsReset(); - } - - void TestOverscroll(); - - AsyncPanZoomController::GestureBehavior mGestureBehavior; - RefPtr<MockContentControllerDelayed> mcc; - RefPtr<TestAPZCTreeManager> tm; - RefPtr<TestAsyncPanZoomController> apzc; -}; - -class APZCGestureDetectorTester : public APZCBasicTester { -public: - APZCGestureDetectorTester() - : APZCBasicTester(AsyncPanZoomController::USE_GESTURE_DETECTOR) - { - } - -protected: - FrameMetrics GetPinchableFrameMetrics() - { - FrameMetrics fm; - fm.SetCompositionBounds(ParentLayerRect(200, 200, 100, 200)); - fm.SetScrollableRect(CSSRect(0, 0, 980, 1000)); - fm.SetScrollOffset(CSSPoint(300, 300)); - fm.SetZoom(CSSToParentLayerScale2D(2.0, 2.0)); - // APZC only allows zooming on the root scrollable frame. - fm.SetIsRootContent(true); - // the visible area of the document in CSS pixels is x=300 y=300 w=50 h=100 - return fm; - } - -}; - -/* The InputReceiver template parameter used in the helper functions below needs - * to be a class that implements functions with the signatures: - * nsEventStatus ReceiveInputEvent(const InputData& aEvent, - * ScrollableLayerGuid* aGuid, - * uint64_t* aOutInputBlockId); - * void SetAllowedTouchBehavior(uint64_t aInputBlockId, - * const nsTArray<uint32_t>& aBehaviours); - * The classes that currently implement these are APZCTreeManager and - * TestAsyncPanZoomController. Using this template allows us to test individual - * APZC instances in isolation and also an entire APZ tree, while using the same - * code to dispatch input events. - */ - -// Some helper functions for constructing input event objects suitable to be -// passed either to an APZC (which expects an transformed point), or to an APZTM -// (which expects an untransformed point). We handle both cases by setting both -// the transformed and untransformed fields to the same value. -static SingleTouchData -CreateSingleTouchData(int32_t aIdentifier, int aX, int aY) -{ - SingleTouchData touch(aIdentifier, ScreenIntPoint(aX, aY), ScreenSize(0, 0), 0, 0); - touch.mLocalScreenPoint = ParentLayerPoint(aX, aY); - return touch; -} -static PinchGestureInput -CreatePinchGestureInput(PinchGestureInput::PinchGestureType aType, - int aFocusX, int aFocusY, - float aCurrentSpan, float aPreviousSpan) -{ - PinchGestureInput result(aType, 0, TimeStamp(), ScreenPoint(aFocusX, aFocusY), - aCurrentSpan, aPreviousSpan, 0); - result.mLocalFocusPoint = ParentLayerPoint(aFocusX, aFocusY); - return result; -} - -template<class InputReceiver> static void -SetDefaultAllowedTouchBehavior(const RefPtr<InputReceiver>& aTarget, - uint64_t aInputBlockId, - int touchPoints = 1) -{ - nsTArray<uint32_t> defaultBehaviors; - // use the default value where everything is allowed - for (int i = 0; i < touchPoints; i++) { - defaultBehaviors.AppendElement(mozilla::layers::AllowedTouchBehavior::HORIZONTAL_PAN - | mozilla::layers::AllowedTouchBehavior::VERTICAL_PAN - | mozilla::layers::AllowedTouchBehavior::PINCH_ZOOM - | mozilla::layers::AllowedTouchBehavior::DOUBLE_TAP_ZOOM); - } - aTarget->SetAllowedTouchBehavior(aInputBlockId, defaultBehaviors); -} - -static uint32_t -MillisecondsSinceStartup(TimeStamp aTime) -{ - return (aTime - TestAsyncPanZoomController::GetStartupTime()).ToMilliseconds(); -} - -static MultiTouchInput -CreateMultiTouchInput(MultiTouchInput::MultiTouchType aType, TimeStamp aTime) -{ - return MultiTouchInput(aType, MillisecondsSinceStartup(aTime), aTime, 0); -} - -template<class InputReceiver> static nsEventStatus -TouchDown(const RefPtr<InputReceiver>& aTarget, int aX, int aY, TimeStamp aTime, uint64_t* aOutInputBlockId = nullptr) -{ - MultiTouchInput mti = CreateMultiTouchInput(MultiTouchInput::MULTITOUCH_START, aTime); - mti.mTouches.AppendElement(CreateSingleTouchData(0, aX, aY)); - return aTarget->ReceiveInputEvent(mti, nullptr, aOutInputBlockId); -} - -template<class InputReceiver> static nsEventStatus -TouchMove(const RefPtr<InputReceiver>& aTarget, int aX, int aY, TimeStamp aTime) -{ - MultiTouchInput mti = CreateMultiTouchInput(MultiTouchInput::MULTITOUCH_MOVE, aTime); - mti.mTouches.AppendElement(CreateSingleTouchData(0, aX, aY)); - return aTarget->ReceiveInputEvent(mti, nullptr, nullptr); -} - -template<class InputReceiver> static nsEventStatus -TouchUp(const RefPtr<InputReceiver>& aTarget, int aX, int aY, TimeStamp aTime) -{ - MultiTouchInput mti = CreateMultiTouchInput(MultiTouchInput::MULTITOUCH_END, aTime); - mti.mTouches.AppendElement(CreateSingleTouchData(0, aX, aY)); - return aTarget->ReceiveInputEvent(mti, nullptr, nullptr); -} - -template<class InputReceiver> static void -Tap(const RefPtr<InputReceiver>& aTarget, int aX, int aY, MockContentControllerDelayed* aMcc, - TimeDuration aTapLength, - nsEventStatus (*aOutEventStatuses)[2] = nullptr, - uint64_t* aOutInputBlockId = nullptr) -{ - // Even if the caller doesn't care about the block id, we need it to set the - // allowed touch behaviour below, so make sure aOutInputBlockId is non-null. - uint64_t blockId; - if (!aOutInputBlockId) { - aOutInputBlockId = &blockId; - } - - nsEventStatus status = TouchDown(aTarget, aX, aY, aMcc->Time(), aOutInputBlockId); - if (aOutEventStatuses) { - (*aOutEventStatuses)[0] = status; - } - aMcc->AdvanceBy(aTapLength); - - // If touch-action is enabled then simulate the allowed touch behaviour - // notification that the main thread is supposed to deliver. - if (gfxPrefs::TouchActionEnabled() && status != nsEventStatus_eConsumeNoDefault) { - SetDefaultAllowedTouchBehavior(aTarget, *aOutInputBlockId); - } - - status = TouchUp(aTarget, aX, aY, aMcc->Time()); - if (aOutEventStatuses) { - (*aOutEventStatuses)[1] = status; - } -} - -template<class InputReceiver> static void -TapAndCheckStatus(const RefPtr<InputReceiver>& aTarget, int aX, int aY, - MockContentControllerDelayed* aMcc, TimeDuration aTapLength) -{ - nsEventStatus statuses[2]; - Tap(aTarget, aX, aY, aMcc, aTapLength, &statuses); - EXPECT_EQ(nsEventStatus_eConsumeDoDefault, statuses[0]); - EXPECT_EQ(nsEventStatus_eConsumeDoDefault, statuses[1]); -} - -template<class InputReceiver> static void -Pan(const RefPtr<InputReceiver>& aTarget, - MockContentControllerDelayed* aMcc, - const ScreenPoint& aTouchStart, - const ScreenPoint& aTouchEnd, - bool aKeepFingerDown = false, - nsTArray<uint32_t>* aAllowedTouchBehaviors = nullptr, - nsEventStatus (*aOutEventStatuses)[4] = nullptr, - uint64_t* aOutInputBlockId = nullptr) -{ - // Reduce the touch start and move tolerance to a tiny value. - // We can't use a scoped pref because this value might be read at some later - // time when the events are actually processed, rather than when we deliver - // them. - gfxPrefs::SetAPZTouchStartTolerance(1.0f / 1000.0f); - gfxPrefs::SetAPZTouchMoveTolerance(0.0f); - const int OVERCOME_TOUCH_TOLERANCE = 1; - - const TimeDuration TIME_BETWEEN_TOUCH_EVENT = TimeDuration::FromMilliseconds(50); - - // Even if the caller doesn't care about the block id, we need it to set the - // allowed touch behaviour below, so make sure aOutInputBlockId is non-null. - uint64_t blockId; - if (!aOutInputBlockId) { - aOutInputBlockId = &blockId; - } - - // Make sure the move is large enough to not be handled as a tap - nsEventStatus status = TouchDown(aTarget, aTouchStart.x, aTouchStart.y + OVERCOME_TOUCH_TOLERANCE, aMcc->Time(), aOutInputBlockId); - if (aOutEventStatuses) { - (*aOutEventStatuses)[0] = status; - } - - aMcc->AdvanceBy(TIME_BETWEEN_TOUCH_EVENT); - - // Allowed touch behaviours must be set after sending touch-start. - if (status != nsEventStatus_eConsumeNoDefault) { - if (aAllowedTouchBehaviors) { - EXPECT_EQ(1UL, aAllowedTouchBehaviors->Length()); - aTarget->SetAllowedTouchBehavior(*aOutInputBlockId, *aAllowedTouchBehaviors); - } else if (gfxPrefs::TouchActionEnabled()) { - SetDefaultAllowedTouchBehavior(aTarget, *aOutInputBlockId); - } - } - - status = TouchMove(aTarget, aTouchStart.x, aTouchStart.y, aMcc->Time()); - if (aOutEventStatuses) { - (*aOutEventStatuses)[1] = status; - } - - aMcc->AdvanceBy(TIME_BETWEEN_TOUCH_EVENT); - - status = TouchMove(aTarget, aTouchEnd.x, aTouchEnd.y, aMcc->Time()); - if (aOutEventStatuses) { - (*aOutEventStatuses)[2] = status; - } - - aMcc->AdvanceBy(TIME_BETWEEN_TOUCH_EVENT); - - if (!aKeepFingerDown) { - status = TouchUp(aTarget, aTouchEnd.x, aTouchEnd.y, aMcc->Time()); - } else { - status = nsEventStatus_eIgnore; - } - if (aOutEventStatuses) { - (*aOutEventStatuses)[3] = status; - } - - // Don't increment the time here. Animations started on touch-up, such as - // flings, are affected by elapsed time, and we want to be able to sample - // them immediately after they start, without time having elapsed. -} - -// A version of Pan() that only takes y coordinates rather than (x, y) points -// for the touch start and end points, and uses 10 for the x coordinates. -// This is for convenience, as most tests only need to pan in one direction. -template<class InputReceiver> static void -Pan(const RefPtr<InputReceiver>& aTarget, - MockContentControllerDelayed* aMcc, - int aTouchStartY, - int aTouchEndY, - bool aKeepFingerDown = false, - nsTArray<uint32_t>* aAllowedTouchBehaviors = nullptr, - nsEventStatus (*aOutEventStatuses)[4] = nullptr, - uint64_t* aOutInputBlockId = nullptr) -{ - ::Pan(aTarget, aMcc, ScreenPoint(10, aTouchStartY), ScreenPoint(10, aTouchEndY), - aKeepFingerDown, aAllowedTouchBehaviors, aOutEventStatuses, aOutInputBlockId); -} - -/* - * Dispatches mock touch events to the apzc and checks whether apzc properly - * consumed them and triggered scrolling behavior. - */ -template<class InputReceiver> static void -PanAndCheckStatus(const RefPtr<InputReceiver>& aTarget, - MockContentControllerDelayed* aMcc, - int aTouchStartY, - int aTouchEndY, - bool aExpectConsumed, - nsTArray<uint32_t>* aAllowedTouchBehaviors, - uint64_t* aOutInputBlockId = nullptr) -{ - nsEventStatus statuses[4]; // down, move, move, up - Pan(aTarget, aMcc, aTouchStartY, aTouchEndY, false, aAllowedTouchBehaviors, &statuses, aOutInputBlockId); - - EXPECT_EQ(nsEventStatus_eConsumeDoDefault, statuses[0]); - - nsEventStatus touchMoveStatus; - if (aExpectConsumed) { - touchMoveStatus = nsEventStatus_eConsumeDoDefault; - } else { - touchMoveStatus = nsEventStatus_eIgnore; - } - EXPECT_EQ(touchMoveStatus, statuses[1]); - EXPECT_EQ(touchMoveStatus, statuses[2]); -} - -static void -ApzcPanNoFling(const RefPtr<TestAsyncPanZoomController>& aApzc, - MockContentControllerDelayed* aMcc, - int aTouchStartY, - int aTouchEndY, - uint64_t* aOutInputBlockId = nullptr) -{ - Pan(aApzc, aMcc, aTouchStartY, aTouchEndY, false, nullptr, nullptr, aOutInputBlockId); - aApzc->CancelAnimation(); -} - -template<class InputReceiver> static void -PinchWithPinchInput(const RefPtr<InputReceiver>& aTarget, - int aFocusX, int aFocusY, int aSecondFocusX, int aSecondFocusY, float aScale, - nsEventStatus (*aOutEventStatuses)[3] = nullptr) -{ - nsEventStatus actualStatus = aTarget->ReceiveInputEvent( - CreatePinchGestureInput(PinchGestureInput::PINCHGESTURE_START, - aFocusX, aFocusY, 10.0, 10.0), - nullptr); - if (aOutEventStatuses) { - (*aOutEventStatuses)[0] = actualStatus; - } - actualStatus = aTarget->ReceiveInputEvent( - CreatePinchGestureInput(PinchGestureInput::PINCHGESTURE_SCALE, - aSecondFocusX, aSecondFocusY, 10.0 * aScale, 10.0), - nullptr); - if (aOutEventStatuses) { - (*aOutEventStatuses)[1] = actualStatus; - } - actualStatus = aTarget->ReceiveInputEvent( - CreatePinchGestureInput(PinchGestureInput::PINCHGESTURE_END, - // note: negative values here tell APZC - // not to turn the pinch into a pan - aFocusX, aFocusY, -1.0, -1.0), - nullptr); - if (aOutEventStatuses) { - (*aOutEventStatuses)[2] = actualStatus; - } -} - -template<class InputReceiver> static void -PinchWithPinchInputAndCheckStatus(const RefPtr<InputReceiver>& aTarget, - int aFocusX, int aFocusY, float aScale, - bool aShouldTriggerPinch) -{ - nsEventStatus statuses[3]; // scalebegin, scale, scaleend - PinchWithPinchInput(aTarget, aFocusX, aFocusY, aFocusX, aFocusY, aScale, &statuses); - - nsEventStatus expectedStatus = aShouldTriggerPinch - ? nsEventStatus_eConsumeNoDefault - : nsEventStatus_eIgnore; - EXPECT_EQ(expectedStatus, statuses[0]); - EXPECT_EQ(expectedStatus, statuses[1]); -} - -template<class InputReceiver> static void -PinchWithTouchInput(const RefPtr<InputReceiver>& aTarget, - int aFocusX, int aFocusY, float aScale, - int& inputId, - nsTArray<uint32_t>* aAllowedTouchBehaviors = nullptr, - nsEventStatus (*aOutEventStatuses)[4] = nullptr, - uint64_t* aOutInputBlockId = nullptr) -{ - // Having pinch coordinates in float type may cause problems with high-precision scale values - // since SingleTouchData accepts integer value. But for trivial tests it should be ok. - float pinchLength = 100.0; - float pinchLengthScaled = pinchLength * aScale; - - // Even if the caller doesn't care about the block id, we need it to set the - // allowed touch behaviour below, so make sure aOutInputBlockId is non-null. - uint64_t blockId; - if (!aOutInputBlockId) { - aOutInputBlockId = &blockId; - } - - MultiTouchInput mtiStart = MultiTouchInput(MultiTouchInput::MULTITOUCH_START, 0, TimeStamp(), 0); - mtiStart.mTouches.AppendElement(CreateSingleTouchData(inputId, aFocusX, aFocusY)); - mtiStart.mTouches.AppendElement(CreateSingleTouchData(inputId + 1, aFocusX, aFocusY)); - nsEventStatus status = aTarget->ReceiveInputEvent(mtiStart, aOutInputBlockId); - if (aOutEventStatuses) { - (*aOutEventStatuses)[0] = status; - } - - if (aAllowedTouchBehaviors) { - EXPECT_EQ(2UL, aAllowedTouchBehaviors->Length()); - aTarget->SetAllowedTouchBehavior(*aOutInputBlockId, *aAllowedTouchBehaviors); - } else if (gfxPrefs::TouchActionEnabled()) { - SetDefaultAllowedTouchBehavior(aTarget, *aOutInputBlockId, 2); - } - - MultiTouchInput mtiMove1 = MultiTouchInput(MultiTouchInput::MULTITOUCH_MOVE, 0, TimeStamp(), 0); - mtiMove1.mTouches.AppendElement(CreateSingleTouchData(inputId, aFocusX - pinchLength, aFocusY)); - mtiMove1.mTouches.AppendElement(CreateSingleTouchData(inputId + 1, aFocusX + pinchLength, aFocusY)); - status = aTarget->ReceiveInputEvent(mtiMove1, nullptr); - if (aOutEventStatuses) { - (*aOutEventStatuses)[1] = status; - } - - MultiTouchInput mtiMove2 = MultiTouchInput(MultiTouchInput::MULTITOUCH_MOVE, 0, TimeStamp(), 0); - mtiMove2.mTouches.AppendElement(CreateSingleTouchData(inputId, aFocusX - pinchLengthScaled, aFocusY)); - mtiMove2.mTouches.AppendElement(CreateSingleTouchData(inputId + 1, aFocusX + pinchLengthScaled, aFocusY)); - status = aTarget->ReceiveInputEvent(mtiMove2, nullptr); - if (aOutEventStatuses) { - (*aOutEventStatuses)[2] = status; - } - - MultiTouchInput mtiEnd = MultiTouchInput(MultiTouchInput::MULTITOUCH_END, 0, TimeStamp(), 0); - mtiEnd.mTouches.AppendElement(CreateSingleTouchData(inputId, aFocusX - pinchLengthScaled, aFocusY)); - mtiEnd.mTouches.AppendElement(CreateSingleTouchData(inputId + 1, aFocusX + pinchLengthScaled, aFocusY)); - status = aTarget->ReceiveInputEvent(mtiEnd, nullptr); - if (aOutEventStatuses) { - (*aOutEventStatuses)[3] = status; - } - - inputId += 2; -} - -template<class InputReceiver> static void -PinchWithTouchInputAndCheckStatus(const RefPtr<InputReceiver>& aTarget, - int aFocusX, int aFocusY, float aScale, - int& inputId, bool aShouldTriggerPinch, - nsTArray<uint32_t>* aAllowedTouchBehaviors) -{ - nsEventStatus statuses[4]; // down, move, move, up - PinchWithTouchInput(aTarget, aFocusX, aFocusY, aScale, inputId, aAllowedTouchBehaviors, &statuses); - - nsEventStatus expectedMoveStatus = aShouldTriggerPinch - ? nsEventStatus_eConsumeDoDefault - : nsEventStatus_eIgnore; - EXPECT_EQ(nsEventStatus_eConsumeDoDefault, statuses[0]); - EXPECT_EQ(expectedMoveStatus, statuses[1]); - EXPECT_EQ(expectedMoveStatus, statuses[2]); -} - -class APZCPinchTester : public APZCBasicTester { -public: - explicit APZCPinchTester(AsyncPanZoomController::GestureBehavior aGestureBehavior = AsyncPanZoomController::DEFAULT_GESTURES) - : APZCBasicTester(aGestureBehavior) - { - } - -protected: - FrameMetrics GetPinchableFrameMetrics() - { - FrameMetrics fm; - fm.SetCompositionBounds(ParentLayerRect(200, 200, 100, 200)); - fm.SetScrollableRect(CSSRect(0, 0, 980, 1000)); - fm.SetScrollOffset(CSSPoint(300, 300)); - fm.SetZoom(CSSToParentLayerScale2D(2.0, 2.0)); - // APZC only allows zooming on the root scrollable frame. - fm.SetIsRootContent(true); - // the visible area of the document in CSS pixels is x=300 y=300 w=50 h=100 - return fm; - } - - void DoPinchTest(bool aShouldTriggerPinch, - nsTArray<uint32_t> *aAllowedTouchBehaviors = nullptr) - { - apzc->SetFrameMetrics(GetPinchableFrameMetrics()); - MakeApzcZoomable(); - - if (aShouldTriggerPinch) { - // One repaint request for each gesture. - EXPECT_CALL(*mcc, RequestContentRepaint(_)).Times(2); - } else { - EXPECT_CALL(*mcc, RequestContentRepaint(_)).Times(0); - } - - int touchInputId = 0; - if (mGestureBehavior == AsyncPanZoomController::USE_GESTURE_DETECTOR) { - PinchWithTouchInputAndCheckStatus(apzc, 250, 300, 1.25, touchInputId, aShouldTriggerPinch, aAllowedTouchBehaviors); - } else { - PinchWithPinchInputAndCheckStatus(apzc, 250, 300, 1.25, aShouldTriggerPinch); - } - - FrameMetrics fm = apzc->GetFrameMetrics(); - - if (aShouldTriggerPinch) { - // the visible area of the document in CSS pixels is now x=305 y=310 w=40 h=80 - EXPECT_EQ(2.5f, fm.GetZoom().ToScaleFactor().scale); - EXPECT_EQ(305, fm.GetScrollOffset().x); - EXPECT_EQ(310, fm.GetScrollOffset().y); - } else { - // The frame metrics should stay the same since touch-action:none makes - // apzc ignore pinch gestures. - EXPECT_EQ(2.0f, fm.GetZoom().ToScaleFactor().scale); - EXPECT_EQ(300, fm.GetScrollOffset().x); - EXPECT_EQ(300, fm.GetScrollOffset().y); - } - - // part 2 of the test, move to the top-right corner of the page and pinch and - // make sure we stay in the correct spot - fm.SetZoom(CSSToParentLayerScale2D(2.0, 2.0)); - fm.SetScrollOffset(CSSPoint(930, 5)); - apzc->SetFrameMetrics(fm); - // the visible area of the document in CSS pixels is x=930 y=5 w=50 h=100 - - if (mGestureBehavior == AsyncPanZoomController::USE_GESTURE_DETECTOR) { - PinchWithTouchInputAndCheckStatus(apzc, 250, 300, 0.5, touchInputId, aShouldTriggerPinch, aAllowedTouchBehaviors); - } else { - PinchWithPinchInputAndCheckStatus(apzc, 250, 300, 0.5, aShouldTriggerPinch); - } - - fm = apzc->GetFrameMetrics(); - - if (aShouldTriggerPinch) { - // the visible area of the document in CSS pixels is now x=880 y=0 w=100 h=200 - EXPECT_EQ(1.0f, fm.GetZoom().ToScaleFactor().scale); - EXPECT_EQ(880, fm.GetScrollOffset().x); - EXPECT_EQ(0, fm.GetScrollOffset().y); - } else { - EXPECT_EQ(2.0f, fm.GetZoom().ToScaleFactor().scale); - EXPECT_EQ(930, fm.GetScrollOffset().x); - EXPECT_EQ(5, fm.GetScrollOffset().y); - } - } -}; - -class APZCPinchGestureDetectorTester : public APZCPinchTester { -public: - APZCPinchGestureDetectorTester() - : APZCPinchTester(AsyncPanZoomController::USE_GESTURE_DETECTOR) - { - } -}; - -TEST_F(APZCPinchTester, Pinch_DefaultGestures_NoTouchAction) { - SCOPED_GFX_PREF(TouchActionEnabled, bool, false); - DoPinchTest(true); -} - -TEST_F(APZCPinchGestureDetectorTester, Pinch_UseGestureDetector_NoTouchAction) { - SCOPED_GFX_PREF(TouchActionEnabled, bool, false); - DoPinchTest(true); -} - -TEST_F(APZCPinchGestureDetectorTester, Pinch_UseGestureDetector_TouchActionNone) { - SCOPED_GFX_PREF(TouchActionEnabled, bool, true); - nsTArray<uint32_t> behaviors = { mozilla::layers::AllowedTouchBehavior::NONE, - mozilla::layers::AllowedTouchBehavior::NONE }; - DoPinchTest(false, &behaviors); -} - -TEST_F(APZCPinchGestureDetectorTester, Pinch_UseGestureDetector_TouchActionZoom) { - SCOPED_GFX_PREF(TouchActionEnabled, bool, true); - nsTArray<uint32_t> behaviors; - behaviors.AppendElement(mozilla::layers::AllowedTouchBehavior::PINCH_ZOOM); - behaviors.AppendElement(mozilla::layers::AllowedTouchBehavior::PINCH_ZOOM); - DoPinchTest(true, &behaviors); -} - -TEST_F(APZCPinchGestureDetectorTester, Pinch_UseGestureDetector_TouchActionNotAllowZoom) { - SCOPED_GFX_PREF(TouchActionEnabled, bool, true); - nsTArray<uint32_t> behaviors; - behaviors.AppendElement(mozilla::layers::AllowedTouchBehavior::VERTICAL_PAN); - behaviors.AppendElement(mozilla::layers::AllowedTouchBehavior::PINCH_ZOOM); - DoPinchTest(false, &behaviors); -} - -TEST_F(APZCPinchGestureDetectorTester, Pinch_PreventDefault) { - FrameMetrics originalMetrics = GetPinchableFrameMetrics(); - apzc->SetFrameMetrics(originalMetrics); - - MakeApzcWaitForMainThread(); - MakeApzcZoomable(); - - int touchInputId = 0; - uint64_t blockId = 0; - PinchWithTouchInput(apzc, 250, 300, 1.25, touchInputId, nullptr, nullptr, &blockId); - - // Send the prevent-default notification for the touch block - apzc->ContentReceivedInputBlock(blockId, true); - - // verify the metrics didn't change (i.e. the pinch was ignored) - FrameMetrics fm = apzc->GetFrameMetrics(); - EXPECT_EQ(originalMetrics.GetZoom(), fm.GetZoom()); - EXPECT_EQ(originalMetrics.GetScrollOffset().x, fm.GetScrollOffset().x); - EXPECT_EQ(originalMetrics.GetScrollOffset().y, fm.GetScrollOffset().y); - - apzc->AssertStateIsReset(); -} - -TEST_F(APZCGestureDetectorTester, Pan_After_Pinch) { - SCOPED_GFX_PREF(TouchActionEnabled, bool, false); - - FrameMetrics originalMetrics = GetPinchableFrameMetrics(); - apzc->SetFrameMetrics(originalMetrics); - - MakeApzcZoomable(); - - // Test parameters - float zoomAmount = 1.25; - float pinchLength = 100.0; - float pinchLengthScaled = pinchLength * zoomAmount; - int focusX = 250; - int focusY = 300; - int panDistance = 20; - - int firstFingerId = 0; - int secondFingerId = firstFingerId + 1; - - // Put fingers down - MultiTouchInput mti = MultiTouchInput(MultiTouchInput::MULTITOUCH_START, 0, TimeStamp(), 0); - mti.mTouches.AppendElement(CreateSingleTouchData(firstFingerId, focusX, focusY)); - mti.mTouches.AppendElement(CreateSingleTouchData(secondFingerId, focusX, focusY)); - apzc->ReceiveInputEvent(mti, nullptr); - - // Spread fingers out to enter the pinch state - mti = MultiTouchInput(MultiTouchInput::MULTITOUCH_MOVE, 0, TimeStamp(), 0); - mti.mTouches.AppendElement(CreateSingleTouchData(firstFingerId, focusX - pinchLength, focusY)); - mti.mTouches.AppendElement(CreateSingleTouchData(secondFingerId, focusX + pinchLength, focusY)); - apzc->ReceiveInputEvent(mti, nullptr); - - // Do the actual pinch of 1.25x - mti = MultiTouchInput(MultiTouchInput::MULTITOUCH_MOVE, 0, TimeStamp(), 0); - mti.mTouches.AppendElement(CreateSingleTouchData(firstFingerId, focusX - pinchLengthScaled, focusY)); - mti.mTouches.AppendElement(CreateSingleTouchData(secondFingerId, focusX + pinchLengthScaled, focusY)); - apzc->ReceiveInputEvent(mti, nullptr); - - // Verify that the zoom changed, just to make sure our code above did what it - // was supposed to. - FrameMetrics zoomedMetrics = apzc->GetFrameMetrics(); - float newZoom = zoomedMetrics.GetZoom().ToScaleFactor().scale; - EXPECT_EQ(originalMetrics.GetZoom().ToScaleFactor().scale * zoomAmount, newZoom); - - // Now we lift one finger... - mti = MultiTouchInput(MultiTouchInput::MULTITOUCH_END, 0, TimeStamp(), 0); - mti.mTouches.AppendElement(CreateSingleTouchData(secondFingerId, focusX + pinchLengthScaled, focusY)); - apzc->ReceiveInputEvent(mti, nullptr); - - // ... and pan with the remaining finger. This pan just breaks through the - // distance threshold. - focusY += 40; - mti = MultiTouchInput(MultiTouchInput::MULTITOUCH_MOVE, 0, TimeStamp(), 0); - mti.mTouches.AppendElement(CreateSingleTouchData(firstFingerId, focusX - pinchLengthScaled, focusY)); - apzc->ReceiveInputEvent(mti, nullptr); - - // This one does an actual pan of 20 pixels - focusY += panDistance; - mti = MultiTouchInput(MultiTouchInput::MULTITOUCH_MOVE, 0, TimeStamp(), 0); - mti.mTouches.AppendElement(CreateSingleTouchData(firstFingerId, focusX - pinchLengthScaled, focusY)); - apzc->ReceiveInputEvent(mti, nullptr); - - // Lift the remaining finger - mti = MultiTouchInput(MultiTouchInput::MULTITOUCH_END, 0, TimeStamp(), 0); - mti.mTouches.AppendElement(CreateSingleTouchData(firstFingerId, focusX - pinchLengthScaled, focusY)); - apzc->ReceiveInputEvent(mti, nullptr); - - // Verify that we scrolled - FrameMetrics finalMetrics = apzc->GetFrameMetrics(); - EXPECT_EQ(zoomedMetrics.GetScrollOffset().y - (panDistance / newZoom), finalMetrics.GetScrollOffset().y); - - // Clear out any remaining fling animation and pending tasks - apzc->AdvanceAnimationsUntilEnd(); - while (mcc->RunThroughDelayedTasks()); - 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; - fm.SetCompositionBounds(ParentLayerRect(0, 0, 100, 100)); - fm.SetScrollableRect(CSSRect(0, 0, 125, 150)); - fm.SetScrollOffset(CSSPoint(10, 0)); - fm.SetZoom(CSSToParentLayerScale2D(1.0, 1.0)); - fm.SetIsRootContent(true); - apzc->SetFrameMetrics(fm); - - MakeApzcZoomable(); - - EXPECT_CALL(*mcc, RequestContentRepaint(_)).Times(1); - - PinchWithPinchInputAndCheckStatus(apzc, 50, 50, 0.5, true); - - fm = apzc->GetFrameMetrics(); - EXPECT_EQ(0.8f, fm.GetZoom().ToScaleFactor().scale); - // bug 936721 - PGO builds introduce rounding error so - // use a fuzzy match instead - EXPECT_LT(std::abs(fm.GetScrollOffset().x), 1e-5); - EXPECT_LT(std::abs(fm.GetScrollOffset().y), 1e-5); -} - -TEST_F(APZCBasicTester, SimpleTransform) { - ParentLayerPoint pointOut; - AsyncTransform viewTransformOut; - apzc->SampleContentTransformForFrame(&viewTransformOut, pointOut); - - EXPECT_EQ(ParentLayerPoint(), pointOut); - EXPECT_EQ(AsyncTransform(), viewTransformOut); -} - - -TEST_F(APZCBasicTester, ComplexTransform) { - // This test assumes there is a page that gets rendered to - // two layers. In CSS pixels, the first layer is 50x50 and - // the second layer is 25x50. The widget scale factor is 3.0 - // and the presShell resolution is 2.0. Therefore, these layers - // end up being 300x300 and 150x300 in layer pixels. - // - // The second (child) layer has an additional CSS transform that - // stretches it by 2.0 on the x-axis. Therefore, after applying - // CSS transforms, the two layers are the same size in screen - // pixels. - // - // The screen itself is 24x24 in screen pixels (therefore 4x4 in - // CSS pixels). The displayport is 1 extra CSS pixel on all - // sides. - - RefPtr<TestAsyncPanZoomController> childApzc = - new TestAsyncPanZoomController(0, mcc, tm); - - const char* layerTreeSyntax = "c(c)"; - // LayerID 0 1 - nsIntRegion layerVisibleRegion[] = { - nsIntRegion(IntRect(0, 0, 300, 300)), - nsIntRegion(IntRect(0, 0, 150, 300)), - }; - Matrix4x4 transforms[] = { - Matrix4x4(), - Matrix4x4(), - }; - transforms[0].PostScale(0.5f, 0.5f, 1.0f); // this results from the 2.0 resolution on the root layer - transforms[1].PostScale(2.0f, 1.0f, 1.0f); // this is the 2.0 x-axis CSS transform on the child layer - - nsTArray<RefPtr<Layer> > layers; - RefPtr<LayerManager> lm; - RefPtr<Layer> root = CreateLayerTree(layerTreeSyntax, layerVisibleRegion, transforms, lm, layers); - - FrameMetrics metrics; - metrics.SetCompositionBounds(ParentLayerRect(0, 0, 24, 24)); - metrics.SetDisplayPort(CSSRect(-1, -1, 6, 6)); - metrics.SetScrollOffset(CSSPoint(10, 10)); - metrics.SetScrollableRect(CSSRect(0, 0, 50, 50)); - metrics.SetCumulativeResolution(LayoutDeviceToLayerScale2D(2, 2)); - metrics.SetPresShellResolution(2.0f); - metrics.SetZoom(CSSToParentLayerScale2D(6, 6)); - metrics.SetDevPixelsPerCSSPixel(CSSToLayoutDeviceScale(3)); - metrics.SetScrollId(FrameMetrics::START_SCROLL_ID); - - FrameMetrics childMetrics = metrics; - childMetrics.SetScrollId(FrameMetrics::START_SCROLL_ID + 1); - - layers[0]->SetFrameMetrics(metrics); - layers[1]->SetFrameMetrics(childMetrics); - - ParentLayerPoint pointOut; - AsyncTransform viewTransformOut; - - // Both the parent and child layer should behave exactly the same here, because - // the CSS transform on the child layer does not affect the SampleContentTransformForFrame code - - // initial transform - apzc->SetFrameMetrics(metrics); - apzc->NotifyLayersUpdated(metrics, true, true); - apzc->SampleContentTransformForFrame(&viewTransformOut, pointOut); - EXPECT_EQ(AsyncTransform(LayerToParentLayerScale(1), ParentLayerPoint()), viewTransformOut); - EXPECT_EQ(ParentLayerPoint(60, 60), pointOut); - - childApzc->SetFrameMetrics(childMetrics); - childApzc->NotifyLayersUpdated(childMetrics, true, true); - childApzc->SampleContentTransformForFrame(&viewTransformOut, pointOut); - EXPECT_EQ(AsyncTransform(LayerToParentLayerScale(1), ParentLayerPoint()), viewTransformOut); - EXPECT_EQ(ParentLayerPoint(60, 60), pointOut); - - // do an async scroll by 5 pixels and check the transform - metrics.ScrollBy(CSSPoint(5, 0)); - apzc->SetFrameMetrics(metrics); - apzc->SampleContentTransformForFrame(&viewTransformOut, pointOut); - EXPECT_EQ(AsyncTransform(LayerToParentLayerScale(1), ParentLayerPoint(-30, 0)), viewTransformOut); - EXPECT_EQ(ParentLayerPoint(90, 60), pointOut); - - childMetrics.ScrollBy(CSSPoint(5, 0)); - childApzc->SetFrameMetrics(childMetrics); - childApzc->SampleContentTransformForFrame(&viewTransformOut, pointOut); - EXPECT_EQ(AsyncTransform(LayerToParentLayerScale(1), ParentLayerPoint(-30, 0)), viewTransformOut); - EXPECT_EQ(ParentLayerPoint(90, 60), pointOut); - - // do an async zoom of 1.5x and check the transform - metrics.ZoomBy(1.5f); - apzc->SetFrameMetrics(metrics); - apzc->SampleContentTransformForFrame(&viewTransformOut, pointOut); - EXPECT_EQ(AsyncTransform(LayerToParentLayerScale(1.5), ParentLayerPoint(-45, 0)), viewTransformOut); - EXPECT_EQ(ParentLayerPoint(135, 90), pointOut); - - childMetrics.ZoomBy(1.5f); - childApzc->SetFrameMetrics(childMetrics); - childApzc->SampleContentTransformForFrame(&viewTransformOut, pointOut); - EXPECT_EQ(AsyncTransform(LayerToParentLayerScale(1.5), ParentLayerPoint(-45, 0)), viewTransformOut); - EXPECT_EQ(ParentLayerPoint(135, 90), pointOut); - - childApzc->Destroy(); -} - -TEST_F(APZCPinchTester, Panning_TwoFinger_ZoomDisabled) { - // set up APZ - apzc->SetFrameMetrics(GetPinchableFrameMetrics()); - MakeApzcUnzoomable(); - - nsEventStatus statuses[3]; // scalebegin, scale, scaleend - PinchWithPinchInput(apzc, 250, 350, 200, 300, 10, &statuses); - - FrameMetrics fm = apzc->GetFrameMetrics(); - - // It starts from (300, 300), then moves the focus point from (250, 350) to - // (200, 300) pans by (50, 50) screen pixels, but there is a 2x zoom, which - // causes the scroll offset to change by half of that (25, 25) pixels. - EXPECT_EQ(325, fm.GetScrollOffset().x); - EXPECT_EQ(325, fm.GetScrollOffset().y); - EXPECT_EQ(2.0, fm.GetZoom().ToScaleFactor().scale); -} - -class APZCPanningTester : public APZCBasicTester { -protected: - void DoPanTest(bool aShouldTriggerScroll, bool aShouldBeConsumed, uint32_t aBehavior) - { - if (aShouldTriggerScroll) { - // One repaint request for each pan. - EXPECT_CALL(*mcc, RequestContentRepaint(_)).Times(2); - } else { - EXPECT_CALL(*mcc, RequestContentRepaint(_)).Times(0); - } - - int touchStart = 50; - int touchEnd = 10; - ParentLayerPoint pointOut; - AsyncTransform viewTransformOut; - - nsTArray<uint32_t> allowedTouchBehaviors; - allowedTouchBehaviors.AppendElement(aBehavior); - - // Pan down - PanAndCheckStatus(apzc, mcc, touchStart, touchEnd, aShouldBeConsumed, &allowedTouchBehaviors); - apzc->SampleContentTransformForFrame(&viewTransformOut, pointOut); - - if (aShouldTriggerScroll) { - EXPECT_EQ(ParentLayerPoint(0, -(touchEnd-touchStart)), pointOut); - EXPECT_NE(AsyncTransform(), viewTransformOut); - } else { - EXPECT_EQ(ParentLayerPoint(), pointOut); - EXPECT_EQ(AsyncTransform(), viewTransformOut); - } - - // Clear the fling from the previous pan, or stopping it will - // consume the next touchstart - apzc->CancelAnimation(); - - // Pan back - PanAndCheckStatus(apzc, mcc, touchEnd, touchStart, aShouldBeConsumed, &allowedTouchBehaviors); - apzc->SampleContentTransformForFrame(&viewTransformOut, pointOut); - - EXPECT_EQ(ParentLayerPoint(), pointOut); - EXPECT_EQ(AsyncTransform(), viewTransformOut); - } - - void DoPanWithPreventDefaultTest() - { - MakeApzcWaitForMainThread(); - - int touchStart = 50; - int touchEnd = 10; - ParentLayerPoint pointOut; - AsyncTransform viewTransformOut; - uint64_t blockId = 0; - - // Pan down - nsTArray<uint32_t> allowedTouchBehaviors; - allowedTouchBehaviors.AppendElement(mozilla::layers::AllowedTouchBehavior::VERTICAL_PAN); - PanAndCheckStatus(apzc, mcc, touchStart, touchEnd, true, &allowedTouchBehaviors, &blockId); - - // Send the signal that content has handled and preventDefaulted the touch - // events. This flushes the event queue. - apzc->ContentReceivedInputBlock(blockId, true); - - apzc->SampleContentTransformForFrame(&viewTransformOut, pointOut); - EXPECT_EQ(ParentLayerPoint(), pointOut); - EXPECT_EQ(AsyncTransform(), viewTransformOut); - - apzc->AssertStateIsReset(); - } -}; - -TEST_F(APZCPanningTester, Pan) { - SCOPED_GFX_PREF(TouchActionEnabled, bool, false); - DoPanTest(true, true, mozilla::layers::AllowedTouchBehavior::NONE); -} - -// In the each of the following 4 pan tests we are performing two pan gestures: vertical pan from top -// to bottom and back - from bottom to top. -// According to the pointer-events/touch-action spec AUTO and PAN_Y touch-action values allow vertical -// scrolling while NONE and PAN_X forbid it. The first parameter of DoPanTest method specifies this -// behavior. -// However, the events will be marked as consumed even if the behavior in PAN_X, because the user could -// move their finger horizontally too - APZ has no way of knowing beforehand and so must consume the -// events. -TEST_F(APZCPanningTester, PanWithTouchActionAuto) { - SCOPED_GFX_PREF(TouchActionEnabled, bool, true); - DoPanTest(true, true, mozilla::layers::AllowedTouchBehavior::HORIZONTAL_PAN - | mozilla::layers::AllowedTouchBehavior::VERTICAL_PAN); -} - -TEST_F(APZCPanningTester, PanWithTouchActionNone) { - SCOPED_GFX_PREF(TouchActionEnabled, bool, true); - DoPanTest(false, false, 0); -} - -TEST_F(APZCPanningTester, PanWithTouchActionPanX) { - SCOPED_GFX_PREF(TouchActionEnabled, bool, true); - DoPanTest(false, true, mozilla::layers::AllowedTouchBehavior::HORIZONTAL_PAN); -} - -TEST_F(APZCPanningTester, PanWithTouchActionPanY) { - SCOPED_GFX_PREF(TouchActionEnabled, bool, true); - DoPanTest(true, true, mozilla::layers::AllowedTouchBehavior::VERTICAL_PAN); -} - -TEST_F(APZCPanningTester, PanWithPreventDefaultAndTouchAction) { - SCOPED_GFX_PREF(TouchActionEnabled, bool, true); - DoPanWithPreventDefaultTest(); -} - -TEST_F(APZCPanningTester, PanWithPreventDefault) { - SCOPED_GFX_PREF(TouchActionEnabled, bool, false); - DoPanWithPreventDefaultTest(); -} - -TEST_F(APZCBasicTester, Fling) { - int touchStart = 50; - int touchEnd = 10; - ParentLayerPoint pointOut; - AsyncTransform viewTransformOut; - - // Fling down. Each step scroll further down - Pan(apzc, mcc, touchStart, touchEnd); - ParentLayerPoint lastPoint; - for (int i = 1; i < 50; i+=1) { - apzc->SampleContentTransformForFrame(&viewTransformOut, pointOut, TimeDuration::FromMilliseconds(1)); - EXPECT_GT(pointOut.y, lastPoint.y); - lastPoint = pointOut; - } -} - -TEST_F(APZCBasicTester, FlingIntoOverscroll) { - // Enable overscrolling. - SCOPED_GFX_PREF(APZOverscrollEnabled, bool, true); - - // Scroll down by 25 px. Don't fling for simplicity. - ApzcPanNoFling(apzc, mcc, 50, 25); - - // Now scroll back up by 20px, this time flinging after. - // The fling should cover the remaining 5 px of room to scroll, then - // go into overscroll, and finally snap-back to recover from overscroll. - Pan(apzc, mcc, 25, 45); - const TimeDuration increment = TimeDuration::FromMilliseconds(1); - bool reachedOverscroll = false; - bool recoveredFromOverscroll = false; - while (apzc->AdvanceAnimations(mcc->Time())) { - if (!reachedOverscroll && apzc->IsOverscrolled()) { - reachedOverscroll = true; - } - if (reachedOverscroll && !apzc->IsOverscrolled()) { - recoveredFromOverscroll = true; - } - mcc->AdvanceBy(increment); - } - EXPECT_TRUE(reachedOverscroll); - EXPECT_TRUE(recoveredFromOverscroll); -} - -TEST_F(APZCBasicTester, PanningTransformNotifications) { - SCOPED_GFX_PREF(APZOverscrollEnabled, bool, true); - - // Scroll down by 25 px. Ensure we only get one set of - // state change notifications. - // - // Then, scroll back up by 20px, this time flinging after. - // The fling should cover the remaining 5 px of room to scroll, then - // go into overscroll, and finally snap-back to recover from overscroll. - // Again, ensure we only get one set of state change notifications for - // this entire procedure. - - MockFunction<void(std::string checkPointName)> check; - { - InSequence s; - EXPECT_CALL(check, Call("Simple pan")); - EXPECT_CALL(*mcc, NotifyAPZStateChange(_,GeckoContentController::APZStateChange::StartTouch,_)).Times(1); - EXPECT_CALL(*mcc, NotifyAPZStateChange(_,GeckoContentController::APZStateChange::TransformBegin,_)).Times(1); - EXPECT_CALL(*mcc, NotifyAPZStateChange(_,GeckoContentController::APZStateChange::StartPanning,_)).Times(1); - EXPECT_CALL(*mcc, NotifyAPZStateChange(_,GeckoContentController::APZStateChange::EndTouch,_)).Times(1); - EXPECT_CALL(*mcc, NotifyAPZStateChange(_,GeckoContentController::APZStateChange::TransformEnd,_)).Times(1); - EXPECT_CALL(check, Call("Complex pan")); - EXPECT_CALL(*mcc, NotifyAPZStateChange(_,GeckoContentController::APZStateChange::StartTouch,_)).Times(1); - EXPECT_CALL(*mcc, NotifyAPZStateChange(_,GeckoContentController::APZStateChange::TransformBegin,_)).Times(1); - EXPECT_CALL(*mcc, NotifyAPZStateChange(_,GeckoContentController::APZStateChange::StartPanning,_)).Times(1); - EXPECT_CALL(*mcc, NotifyAPZStateChange(_,GeckoContentController::APZStateChange::EndTouch,_)).Times(1); - EXPECT_CALL(*mcc, NotifyAPZStateChange(_,GeckoContentController::APZStateChange::TransformEnd,_)).Times(1); - EXPECT_CALL(check, Call("Done")); - } - - check.Call("Simple pan"); - ApzcPanNoFling(apzc, mcc, 50, 25); - check.Call("Complex pan"); - Pan(apzc, mcc, 25, 45); - apzc->AdvanceAnimationsUntilEnd(); - check.Call("Done"); -} - -void APZCBasicTester::PanIntoOverscroll() -{ - int touchStart = 500; - int touchEnd = 10; - Pan(apzc, mcc, touchStart, touchEnd); - EXPECT_TRUE(apzc->IsOverscrolled()); -} - -void APZCBasicTester::TestOverscroll() -{ - // Pan sufficiently to hit overscroll behavior - PanIntoOverscroll(); - - // Check that we recover from overscroll via an animation. - ParentLayerPoint expectedScrollOffset(0, GetScrollRange().YMost()); - SampleAnimationUntilRecoveredFromOverscroll(expectedScrollOffset); -} - - -TEST_F(APZCBasicTester, OverScrollPanning) { - SCOPED_GFX_PREF(APZOverscrollEnabled, bool, true); - - TestOverscroll(); -} - -// Tests that an overscroll animation doesn't trigger an assertion failure -// in the case where a sample has a velocity of zero. -TEST_F(APZCBasicTester, OverScroll_Bug1152051a) { - SCOPED_GFX_PREF(APZOverscrollEnabled, bool, true); - - // Doctor the prefs to make the velocity zero at the end of the first sample. - - // This ensures our incoming velocity to the overscroll animation is - // a round(ish) number, 4.9 (that being the distance of the pan before - // overscroll, which is 500 - 10 = 490 pixels, divided by the duration of - // the pan, which is 100 ms). - SCOPED_GFX_PREF(APZFlingFriction, float, 0); - - // To ensure the velocity after the first sample is 0, set the spring - // stiffness to the incoming velocity (4.9) divided by the overscroll - // (400 pixels) times the step duration (1 ms). - SCOPED_GFX_PREF(APZOverscrollSpringStiffness, float, 0.01225f); - - TestOverscroll(); -} - -// Tests that ending an overscroll animation doesn't leave around state that -// confuses the next overscroll animation. -TEST_F(APZCBasicTester, OverScroll_Bug1152051b) { - SCOPED_GFX_PREF(APZOverscrollEnabled, bool, true); - - SCOPED_GFX_PREF(APZOverscrollStopDistanceThreshold, float, 0.1f); - - // Pan sufficiently to hit overscroll behavior - PanIntoOverscroll(); - - // Sample animations once, to give the fling animation started on touch-up - // a chance to realize it's overscrolled, and schedule a call to - // HandleFlingOverscroll(). - SampleAnimationOnce(); - - // This advances the time and runs the HandleFlingOverscroll task scheduled in - // the previous call, which starts an overscroll animation. It then samples - // the overscroll animation once, to get it to initialize the first overscroll - // sample. - SampleAnimationOnce(); - - // Do a touch-down to cancel the overscroll animation, and then a touch-up - // to schedule a new one since we're still overscrolled. We don't pan because - // panning can trigger functions that clear the overscroll animation state - // in other ways. - TouchDown(apzc, 10, 10, mcc->Time(), nullptr); - TouchUp(apzc, 10, 10, mcc->Time()); - - // Sample the second overscroll animation to its end. - // If the ending of the first overscroll animation fails to clear state - // properly, this will assert. - ParentLayerPoint expectedScrollOffset(0, GetScrollRange().YMost()); - SampleAnimationUntilRecoveredFromOverscroll(expectedScrollOffset); -} - -TEST_F(APZCBasicTester, OverScrollAbort) { - SCOPED_GFX_PREF(APZOverscrollEnabled, bool, true); - - // Pan sufficiently to hit overscroll behavior - int touchStart = 500; - int touchEnd = 10; - Pan(apzc, mcc, touchStart, touchEnd); - EXPECT_TRUE(apzc->IsOverscrolled()); - - ParentLayerPoint pointOut; - AsyncTransform viewTransformOut; - - // This sample call will run to the end of the fling animation - // and will schedule the overscroll animation. - apzc->SampleContentTransformForFrame(&viewTransformOut, pointOut, TimeDuration::FromMilliseconds(10000)); - EXPECT_TRUE(apzc->IsOverscrolled()); - - // At this point, we have an active overscroll animation. - // Check that cancelling the animation clears the overscroll. - apzc->CancelAnimation(); - EXPECT_FALSE(apzc->IsOverscrolled()); - apzc->AssertStateIsReset(); -} - -TEST_F(APZCBasicTester, OverScrollPanningAbort) { - SCOPED_GFX_PREF(APZOverscrollEnabled, bool, true); - - // Pan sufficiently to hit overscroll behaviour. Keep the finger down so - // the pan does not end. - int touchStart = 500; - int touchEnd = 10; - Pan(apzc, mcc, touchStart, touchEnd, true); // keep finger down - EXPECT_TRUE(apzc->IsOverscrolled()); - - // Check that calling CancelAnimation() while the user is still panning - // (and thus no fling or snap-back animation has had a chance to start) - // clears the overscroll. - apzc->CancelAnimation(); - EXPECT_FALSE(apzc->IsOverscrolled()); - apzc->AssertStateIsReset(); -} - - -class APZCFlingStopTester : public APZCGestureDetectorTester { -protected: - // Start a fling, and then tap while the fling is ongoing. When - // aSlow is false, the tap will happen while the fling is at a - // high velocity, and we check that the tap doesn't trigger sending a tap - // to content. If aSlow is true, the tap will happen while the fling - // is at a slow velocity, and we check that the tap does trigger sending - // a tap to content. See bug 1022956. - void DoFlingStopTest(bool aSlow) { - int touchStart = 50; - int touchEnd = 10; - - // Start the fling down. - Pan(apzc, mcc, touchStart, touchEnd); - // The touchstart from the pan will leave some cancelled tasks in the queue, clear them out - - // If we want to tap while the fling is fast, let the fling advance for 10ms only. If we want - // the fling to slow down more, advance to 2000ms. These numbers may need adjusting if our - // friction and threshold values change, but they should be deterministic at least. - int timeDelta = aSlow ? 2000 : 10; - int tapCallsExpected = aSlow ? 2 : 1; - - // Advance the fling animation by timeDelta milliseconds. - ParentLayerPoint pointOut; - AsyncTransform viewTransformOut; - apzc->SampleContentTransformForFrame(&viewTransformOut, pointOut, TimeDuration::FromMilliseconds(timeDelta)); - - // Deliver a tap to abort the fling. Ensure that we get a HandleSingleTap - // call out of it if and only if the fling is slow. - EXPECT_CALL(*mcc, HandleSingleTap(_, 0, apzc->GetGuid())).Times(tapCallsExpected); - Tap(apzc, 10, 10, mcc, 0); - while (mcc->RunThroughDelayedTasks()); - - // Deliver another tap, to make sure that taps are flowing properly once - // the fling is aborted. - Tap(apzc, 100, 100, mcc, 0); - while (mcc->RunThroughDelayedTasks()); - - // Verify that we didn't advance any further after the fling was aborted, in either case. - ParentLayerPoint finalPointOut; - apzc->SampleContentTransformForFrame(&viewTransformOut, finalPointOut); - EXPECT_EQ(pointOut.x, finalPointOut.x); - EXPECT_EQ(pointOut.y, finalPointOut.y); - - apzc->AssertStateIsReset(); - } - - void DoFlingStopWithSlowListener(bool aPreventDefault) { - MakeApzcWaitForMainThread(); - - int touchStart = 50; - int touchEnd = 10; - uint64_t blockId = 0; - - // Start the fling down. - Pan(apzc, mcc, touchStart, touchEnd, false, nullptr, nullptr, &blockId); - apzc->ConfirmTarget(blockId); - apzc->ContentReceivedInputBlock(blockId, false); - - // Sample the fling a couple of times to ensure it's going. - ParentLayerPoint point, finalPoint; - AsyncTransform viewTransform; - apzc->SampleContentTransformForFrame(&viewTransform, point, TimeDuration::FromMilliseconds(10)); - apzc->SampleContentTransformForFrame(&viewTransform, finalPoint, TimeDuration::FromMilliseconds(10)); - EXPECT_GT(finalPoint.y, point.y); - - // Now we put our finger down to stop the fling - TouchDown(apzc, 10, 10, mcc->Time(), &blockId); - - // Re-sample to make sure it hasn't moved - apzc->SampleContentTransformForFrame(&viewTransform, point, TimeDuration::FromMilliseconds(10)); - EXPECT_EQ(finalPoint.x, point.x); - EXPECT_EQ(finalPoint.y, point.y); - - // respond to the touchdown that stopped the fling. - // even if we do a prevent-default on it, the animation should remain stopped. - apzc->ContentReceivedInputBlock(blockId, aPreventDefault); - - // Verify the page hasn't moved - apzc->SampleContentTransformForFrame(&viewTransform, point, TimeDuration::FromMilliseconds(70)); - EXPECT_EQ(finalPoint.x, point.x); - EXPECT_EQ(finalPoint.y, point.y); - - // clean up - TouchUp(apzc, 10, 10, mcc->Time()); - - apzc->AssertStateIsReset(); - } -}; - -TEST_F(APZCFlingStopTester, FlingStop) { - DoFlingStopTest(false); -} - -TEST_F(APZCFlingStopTester, FlingStopTap) { - DoFlingStopTest(true); -} - -TEST_F(APZCFlingStopTester, FlingStopSlowListener) { - DoFlingStopWithSlowListener(false); -} - -TEST_F(APZCFlingStopTester, FlingStopPreventDefault) { - DoFlingStopWithSlowListener(true); -} - -TEST_F(APZCGestureDetectorTester, ShortPress) { - MakeApzcUnzoomable(); - - MockFunction<void(std::string checkPointName)> check; - { - InSequence s; - // This verifies that the single tap notification is sent after the - // touchup is fully processed. The ordering here is important. - EXPECT_CALL(check, Call("pre-tap")); - EXPECT_CALL(check, Call("post-tap")); - EXPECT_CALL(*mcc, HandleSingleTap(CSSPoint(10, 10), 0, apzc->GetGuid())).Times(1); - } - - check.Call("pre-tap"); - TapAndCheckStatus(apzc, 10, 10, mcc, TimeDuration::FromMilliseconds(100)); - check.Call("post-tap"); - - apzc->AssertStateIsReset(); -} - -TEST_F(APZCGestureDetectorTester, MediumPress) { - MakeApzcUnzoomable(); - - MockFunction<void(std::string checkPointName)> check; - { - InSequence s; - // This verifies that the single tap notification is sent after the - // touchup is fully processed. The ordering here is important. - EXPECT_CALL(check, Call("pre-tap")); - EXPECT_CALL(check, Call("post-tap")); - EXPECT_CALL(*mcc, HandleSingleTap(CSSPoint(10, 10), 0, apzc->GetGuid())).Times(1); - } - - check.Call("pre-tap"); - TapAndCheckStatus(apzc, 10, 10, mcc, TimeDuration::FromMilliseconds(400)); - check.Call("post-tap"); - - apzc->AssertStateIsReset(); -} - -class APZCLongPressTester : public APZCGestureDetectorTester { -protected: - void DoLongPressTest(uint32_t aBehavior) { - MakeApzcUnzoomable(); - - uint64_t blockId = 0; - - nsEventStatus status = TouchDown(apzc, 10, 10, mcc->Time(), &blockId); - EXPECT_EQ(nsEventStatus_eConsumeDoDefault, status); - - if (gfxPrefs::TouchActionEnabled() && status != nsEventStatus_eConsumeNoDefault) { - // SetAllowedTouchBehavior() must be called after sending touch-start. - nsTArray<uint32_t> allowedTouchBehaviors; - allowedTouchBehaviors.AppendElement(aBehavior); - apzc->SetAllowedTouchBehavior(blockId, allowedTouchBehaviors); - } - // Have content "respond" to the touchstart - apzc->ContentReceivedInputBlock(blockId, false); - - MockFunction<void(std::string checkPointName)> check; - - { - InSequence s; - - EXPECT_CALL(check, Call("preHandleLongTap")); - blockId++; - EXPECT_CALL(*mcc, HandleLongTap(CSSPoint(10, 10), 0, apzc->GetGuid(), blockId)).Times(1); - EXPECT_CALL(check, Call("postHandleLongTap")); - - EXPECT_CALL(check, Call("preHandleSingleTap")); - EXPECT_CALL(*mcc, HandleSingleTap(CSSPoint(10, 10), 0, apzc->GetGuid())).Times(1); - EXPECT_CALL(check, Call("postHandleSingleTap")); - } - - // Manually invoke the longpress while the touch is currently down. - check.Call("preHandleLongTap"); - mcc->RunThroughDelayedTasks(); - check.Call("postHandleLongTap"); - - // Dispatching the longpress event starts a new touch block, which - // needs a new content response and also has a pending timeout task - // in the queue. Deal with those here. We do the content response first - // with preventDefault=false, and then we run the timeout task which - // "loses the race" and does nothing. - apzc->ContentReceivedInputBlock(blockId, false); - mcc->AdvanceByMillis(1000); - - // Finally, simulate lifting the finger. Since the long-press wasn't - // prevent-defaulted, we should get a long-tap-up event. - check.Call("preHandleSingleTap"); - status = TouchUp(apzc, 10, 10, mcc->Time()); - mcc->RunThroughDelayedTasks(); - EXPECT_EQ(nsEventStatus_eConsumeDoDefault, status); - check.Call("postHandleSingleTap"); - - apzc->AssertStateIsReset(); - } - - void DoLongPressPreventDefaultTest(uint32_t aBehavior) { - MakeApzcUnzoomable(); - - EXPECT_CALL(*mcc, RequestContentRepaint(_)).Times(0); - - int touchX = 10, - touchStartY = 10, - touchEndY = 50; - - uint64_t blockId = 0; - nsEventStatus status = TouchDown(apzc, touchX, touchStartY, mcc->Time(), &blockId); - EXPECT_EQ(nsEventStatus_eConsumeDoDefault, status); - - if (gfxPrefs::TouchActionEnabled() && status != nsEventStatus_eConsumeNoDefault) { - // SetAllowedTouchBehavior() must be called after sending touch-start. - nsTArray<uint32_t> allowedTouchBehaviors; - allowedTouchBehaviors.AppendElement(aBehavior); - apzc->SetAllowedTouchBehavior(blockId, allowedTouchBehaviors); - } - // Have content "respond" to the touchstart - apzc->ContentReceivedInputBlock(blockId, false); - - MockFunction<void(std::string checkPointName)> check; - - { - InSequence s; - - EXPECT_CALL(check, Call("preHandleLongTap")); - blockId++; - EXPECT_CALL(*mcc, HandleLongTap(CSSPoint(touchX, touchStartY), 0, apzc->GetGuid(), blockId)).Times(1); - EXPECT_CALL(check, Call("postHandleLongTap")); - } - - // Manually invoke the longpress while the touch is currently down. - check.Call("preHandleLongTap"); - mcc->RunThroughDelayedTasks(); - check.Call("postHandleLongTap"); - - // There should be a TimeoutContentResponse task in the queue still, - // waiting for the response from the longtap event dispatched above. - // Send the signal that content has handled the long-tap, and then run - // the timeout task (it will be a no-op because the content "wins" the - // race. This takes the place of the "contextmenu" event. - apzc->ContentReceivedInputBlock(blockId, true); - mcc->AdvanceByMillis(1000); - - MultiTouchInput mti = CreateMultiTouchInput(MultiTouchInput::MULTITOUCH_MOVE, mcc->Time()); - mti.mTouches.AppendElement(SingleTouchData(0, ParentLayerPoint(touchX, touchEndY), ScreenSize(0, 0), 0, 0)); - status = apzc->ReceiveInputEvent(mti, nullptr); - EXPECT_EQ(nsEventStatus_eConsumeDoDefault, status); - - EXPECT_CALL(*mcc, HandleSingleTap(CSSPoint(touchX, touchEndY), 0, apzc->GetGuid())).Times(0); - status = TouchUp(apzc, touchX, touchEndY, mcc->Time()); - EXPECT_EQ(nsEventStatus_eConsumeDoDefault, status); - - ParentLayerPoint pointOut; - AsyncTransform viewTransformOut; - apzc->SampleContentTransformForFrame(&viewTransformOut, pointOut); - - EXPECT_EQ(ParentLayerPoint(), pointOut); - EXPECT_EQ(AsyncTransform(), viewTransformOut); - - apzc->AssertStateIsReset(); - } -}; - -TEST_F(APZCLongPressTester, LongPress) { - SCOPED_GFX_PREF(TouchActionEnabled, bool, false); - DoLongPressTest(mozilla::layers::AllowedTouchBehavior::NONE); -} - -TEST_F(APZCLongPressTester, LongPressWithTouchAction) { - SCOPED_GFX_PREF(TouchActionEnabled, bool, true); - DoLongPressTest(mozilla::layers::AllowedTouchBehavior::HORIZONTAL_PAN - | mozilla::layers::AllowedTouchBehavior::VERTICAL_PAN - | mozilla::layers::AllowedTouchBehavior::PINCH_ZOOM); -} - -TEST_F(APZCLongPressTester, LongPressPreventDefault) { - SCOPED_GFX_PREF(TouchActionEnabled, bool, false); - DoLongPressPreventDefaultTest(mozilla::layers::AllowedTouchBehavior::NONE); -} - -TEST_F(APZCLongPressTester, LongPressPreventDefaultWithTouchAction) { - SCOPED_GFX_PREF(TouchActionEnabled, bool, true); - DoLongPressPreventDefaultTest(mozilla::layers::AllowedTouchBehavior::HORIZONTAL_PAN - | mozilla::layers::AllowedTouchBehavior::VERTICAL_PAN - | mozilla::layers::AllowedTouchBehavior::PINCH_ZOOM); -} - -template<class InputReceiver> static void -DoubleTap(const RefPtr<InputReceiver>& aTarget, int aX, int aY, MockContentControllerDelayed* aMcc, - nsEventStatus (*aOutEventStatuses)[4] = nullptr, - uint64_t (*aOutInputBlockIds)[2] = nullptr) -{ - uint64_t blockId; - nsEventStatus status = TouchDown(aTarget, aX, aY, aMcc->Time(), &blockId); - if (aOutEventStatuses) { - (*aOutEventStatuses)[0] = status; - } - if (aOutInputBlockIds) { - (*aOutInputBlockIds)[0] = blockId; - } - aMcc->AdvanceByMillis(10); - - // If touch-action is enabled then simulate the allowed touch behaviour - // notification that the main thread is supposed to deliver. - if (gfxPrefs::TouchActionEnabled() && status != nsEventStatus_eConsumeNoDefault) { - SetDefaultAllowedTouchBehavior(aTarget, blockId); - } - - status = TouchUp(aTarget, aX, aY, aMcc->Time()); - if (aOutEventStatuses) { - (*aOutEventStatuses)[1] = status; - } - aMcc->AdvanceByMillis(10); - status = TouchDown(aTarget, aX, aY, aMcc->Time(), &blockId); - if (aOutEventStatuses) { - (*aOutEventStatuses)[2] = status; - } - if (aOutInputBlockIds) { - (*aOutInputBlockIds)[1] = blockId; - } - aMcc->AdvanceByMillis(10); - - if (gfxPrefs::TouchActionEnabled() && status != nsEventStatus_eConsumeNoDefault) { - SetDefaultAllowedTouchBehavior(aTarget, blockId); - } - - status = TouchUp(aTarget, aX, aY, aMcc->Time()); - if (aOutEventStatuses) { - (*aOutEventStatuses)[3] = status; - } -} - -template<class InputReceiver> static void -DoubleTapAndCheckStatus(const RefPtr<InputReceiver>& aTarget, int aX, int aY, - MockContentControllerDelayed* aMcc, uint64_t (*aOutInputBlockIds)[2] = nullptr) -{ - nsEventStatus statuses[4]; - DoubleTap(aTarget, aX, aY, aMcc, &statuses, aOutInputBlockIds); - EXPECT_EQ(nsEventStatus_eConsumeDoDefault, statuses[0]); - EXPECT_EQ(nsEventStatus_eConsumeDoDefault, statuses[1]); - EXPECT_EQ(nsEventStatus_eConsumeDoDefault, statuses[2]); - EXPECT_EQ(nsEventStatus_eConsumeDoDefault, statuses[3]); -} - -TEST_F(APZCGestureDetectorTester, DoubleTap) { - MakeApzcWaitForMainThread(); - MakeApzcZoomable(); - - EXPECT_CALL(*mcc, HandleSingleTap(CSSPoint(10, 10), 0, apzc->GetGuid())).Times(0); - EXPECT_CALL(*mcc, HandleDoubleTap(CSSPoint(10, 10), 0, apzc->GetGuid())).Times(1); - - uint64_t blockIds[2]; - DoubleTapAndCheckStatus(apzc, 10, 10, mcc, &blockIds); - - // responses to the two touchstarts - apzc->ContentReceivedInputBlock(blockIds[0], false); - apzc->ContentReceivedInputBlock(blockIds[1], false); - - apzc->AssertStateIsReset(); -} - -TEST_F(APZCGestureDetectorTester, DoubleTapNotZoomable) { - MakeApzcWaitForMainThread(); - MakeApzcUnzoomable(); - - EXPECT_CALL(*mcc, HandleSingleTap(CSSPoint(10, 10), 0, apzc->GetGuid())).Times(2); - EXPECT_CALL(*mcc, HandleDoubleTap(CSSPoint(10, 10), 0, apzc->GetGuid())).Times(0); - - uint64_t blockIds[2]; - DoubleTapAndCheckStatus(apzc, 10, 10, mcc, &blockIds); - - // responses to the two touchstarts - apzc->ContentReceivedInputBlock(blockIds[0], false); - apzc->ContentReceivedInputBlock(blockIds[1], false); - - apzc->AssertStateIsReset(); -} - -TEST_F(APZCGestureDetectorTester, DoubleTapPreventDefaultFirstOnly) { - MakeApzcWaitForMainThread(); - MakeApzcZoomable(); - - EXPECT_CALL(*mcc, HandleSingleTap(CSSPoint(10, 10), 0, apzc->GetGuid())).Times(1); - EXPECT_CALL(*mcc, HandleDoubleTap(CSSPoint(10, 10), 0, apzc->GetGuid())).Times(0); - - uint64_t blockIds[2]; - DoubleTapAndCheckStatus(apzc, 10, 10, mcc, &blockIds); - - // responses to the two touchstarts - apzc->ContentReceivedInputBlock(blockIds[0], true); - apzc->ContentReceivedInputBlock(blockIds[1], false); - - apzc->AssertStateIsReset(); -} - -TEST_F(APZCGestureDetectorTester, DoubleTapPreventDefaultBoth) { - MakeApzcWaitForMainThread(); - MakeApzcZoomable(); - - EXPECT_CALL(*mcc, HandleSingleTap(CSSPoint(10, 10), 0, apzc->GetGuid())).Times(0); - EXPECT_CALL(*mcc, HandleDoubleTap(CSSPoint(10, 10), 0, apzc->GetGuid())).Times(0); - - uint64_t blockIds[2]; - DoubleTapAndCheckStatus(apzc, 10, 10, mcc, &blockIds); - - // responses to the two touchstarts - apzc->ContentReceivedInputBlock(blockIds[0], true); - apzc->ContentReceivedInputBlock(blockIds[1], true); - - apzc->AssertStateIsReset(); -} - -// Test for bug 947892 -// We test whether we dispatch tap event when the tap is followed by pinch. -TEST_F(APZCGestureDetectorTester, TapFollowedByPinch) { - MakeApzcZoomable(); - - EXPECT_CALL(*mcc, HandleSingleTap(CSSPoint(10, 10), 0, apzc->GetGuid())).Times(1); - - Tap(apzc, 10, 10, mcc, TimeDuration::FromMilliseconds(100)); - - int inputId = 0; - MultiTouchInput mti; - mti = CreateMultiTouchInput(MultiTouchInput::MULTITOUCH_START, mcc->Time()); - mti.mTouches.AppendElement(SingleTouchData(inputId, ParentLayerPoint(20, 20), ScreenSize(0, 0), 0, 0)); - mti.mTouches.AppendElement(SingleTouchData(inputId + 1, ParentLayerPoint(10, 10), ScreenSize(0, 0), 0, 0)); - apzc->ReceiveInputEvent(mti, nullptr); - - mti = CreateMultiTouchInput(MultiTouchInput::MULTITOUCH_END, mcc->Time()); - mti.mTouches.AppendElement(SingleTouchData(inputId, ParentLayerPoint(20, 20), ScreenSize(0, 0), 0, 0)); - mti.mTouches.AppendElement(SingleTouchData(inputId + 1, ParentLayerPoint(10, 10), ScreenSize(0, 0), 0, 0)); - apzc->ReceiveInputEvent(mti, nullptr); - - apzc->AssertStateIsReset(); -} - -TEST_F(APZCGestureDetectorTester, TapFollowedByMultipleTouches) { - MakeApzcZoomable(); - - EXPECT_CALL(*mcc, HandleSingleTap(CSSPoint(10, 10), 0, apzc->GetGuid())).Times(1); - - Tap(apzc, 10, 10, mcc, TimeDuration::FromMilliseconds(100)); - - int inputId = 0; - MultiTouchInput mti; - mti = CreateMultiTouchInput(MultiTouchInput::MULTITOUCH_START, mcc->Time()); - mti.mTouches.AppendElement(SingleTouchData(inputId, ParentLayerPoint(20, 20), ScreenSize(0, 0), 0, 0)); - apzc->ReceiveInputEvent(mti, nullptr); - - mti = CreateMultiTouchInput(MultiTouchInput::MULTITOUCH_START, mcc->Time()); - mti.mTouches.AppendElement(SingleTouchData(inputId, ParentLayerPoint(20, 20), ScreenSize(0, 0), 0, 0)); - mti.mTouches.AppendElement(SingleTouchData(inputId + 1, ParentLayerPoint(10, 10), ScreenSize(0, 0), 0, 0)); - apzc->ReceiveInputEvent(mti, nullptr); - - mti = CreateMultiTouchInput(MultiTouchInput::MULTITOUCH_END, mcc->Time()); - mti.mTouches.AppendElement(SingleTouchData(inputId, ParentLayerPoint(20, 20), ScreenSize(0, 0), 0, 0)); - mti.mTouches.AppendElement(SingleTouchData(inputId + 1, ParentLayerPoint(10, 10), ScreenSize(0, 0), 0, 0)); - apzc->ReceiveInputEvent(mti, nullptr); - - apzc->AssertStateIsReset(); -} - -class APZCTreeManagerTester : public ::testing::Test { -protected: - virtual void SetUp() { - gfxPrefs::GetSingleton(); - APZThreadUtils::SetThreadAssertionsEnabled(false); - APZThreadUtils::SetControllerThread(MessageLoop::current()); - - mcc = new NiceMock<MockContentControllerDelayed>(); - manager = new TestAPZCTreeManager(mcc); - } - - virtual void TearDown() { - while (mcc->RunThroughDelayedTasks()); - manager->ClearTree(); - } - - /** - * Sample animations once for all APZCs, 1 ms later than the last sample. - */ - void SampleAnimationsOnce() { - const TimeDuration increment = TimeDuration::FromMilliseconds(1); - ParentLayerPoint pointOut; - AsyncTransform viewTransformOut; - mcc->AdvanceBy(increment); - - for (const RefPtr<Layer>& layer : layers) { - if (TestAsyncPanZoomController* apzc = ApzcOf(layer)) { - apzc->SampleContentTransformForFrame(&viewTransformOut, pointOut); - } - } - } - - RefPtr<MockContentControllerDelayed> mcc; - - nsTArray<RefPtr<Layer> > layers; - RefPtr<LayerManager> lm; - RefPtr<Layer> root; - - RefPtr<TestAPZCTreeManager> manager; - -protected: - static void SetScrollableFrameMetrics(Layer* aLayer, FrameMetrics::ViewID aScrollId, - CSSRect aScrollableRect = CSSRect(-1, -1, -1, -1)) { - FrameMetrics metrics; - metrics.SetScrollId(aScrollId); - // By convention in this test file, START_SCROLL_ID is the root, so mark it as such. - if (aScrollId == FrameMetrics::START_SCROLL_ID) { - metrics.SetIsLayersIdRoot(true); - } - IntRect layerBound = aLayer->GetVisibleRegion().ToUnknownRegion().GetBounds(); - metrics.SetCompositionBounds(ParentLayerRect(layerBound.x, layerBound.y, - layerBound.width, layerBound.height)); - metrics.SetScrollableRect(aScrollableRect); - metrics.SetScrollOffset(CSSPoint(0, 0)); - metrics.SetPageScrollAmount(LayoutDeviceIntSize(50, 100)); - metrics.SetAllowVerticalScrollWithWheel(true); - aLayer->SetFrameMetrics(metrics); - aLayer->SetClipRect(Some(ViewAs<ParentLayerPixel>(layerBound))); - if (!aScrollableRect.IsEqualEdges(CSSRect(-1, -1, -1, -1))) { - // The purpose of this is to roughly mimic what layout would do in the - // case of a scrollable frame with the event regions and clip. This lets - // us exercise the hit-testing code in APZCTreeManager - EventRegions er = aLayer->GetEventRegions(); - IntRect scrollRect = RoundedToInt(aScrollableRect * metrics.LayersPixelsPerCSSPixel()).ToUnknownRect(); - er.mHitRegion = nsIntRegion(IntRect(layerBound.TopLeft(), scrollRect.Size())); - aLayer->SetEventRegions(er); - } - } - - void SetScrollHandoff(Layer* aChild, Layer* aParent) { - FrameMetrics metrics = aChild->GetFrameMetrics(0); - metrics.SetScrollParentId(aParent->GetFrameMetrics(0).GetScrollId()); - aChild->SetFrameMetrics(metrics); - } - - static TestAsyncPanZoomController* ApzcOf(Layer* aLayer) { - EXPECT_EQ(1u, aLayer->GetFrameMetricsCount()); - return (TestAsyncPanZoomController*)aLayer->GetAsyncPanZoomController(0); - } - - void CreateSimpleScrollingLayer() { - const char* layerTreeSyntax = "t"; - nsIntRegion layerVisibleRegion[] = { - nsIntRegion(IntRect(0,0,200,200)), - }; - root = CreateLayerTree(layerTreeSyntax, layerVisibleRegion, nullptr, lm, layers); - SetScrollableFrameMetrics(root, FrameMetrics::START_SCROLL_ID, CSSRect(0, 0, 500, 500)); - } - - void CreateSimpleDTCScrollingLayer() { - const char* layerTreeSyntax = "t"; - nsIntRegion layerVisibleRegion[] = { - nsIntRegion(IntRect(0,0,200,200)), - }; - root = CreateLayerTree(layerTreeSyntax, layerVisibleRegion, nullptr, lm, layers); - SetScrollableFrameMetrics(root, FrameMetrics::START_SCROLL_ID, CSSRect(0, 0, 500, 500)); - - EventRegions regions; - regions.mHitRegion = nsIntRegion(IntRect(0, 0, 200, 200)); - regions.mDispatchToContentHitRegion = regions.mHitRegion; - layers[0]->SetEventRegions(regions); - } - - void CreateSimpleMultiLayerTree() { - const char* layerTreeSyntax = "c(tt)"; - // LayerID 0 12 - nsIntRegion layerVisibleRegion[] = { - nsIntRegion(IntRect(0,0,100,100)), - nsIntRegion(IntRect(0,0,100,50)), - nsIntRegion(IntRect(0,50,100,50)), - }; - root = CreateLayerTree(layerTreeSyntax, layerVisibleRegion, nullptr, lm, layers); - } - - void CreatePotentiallyLeakingTree() { - const char* layerTreeSyntax = "c(c(c(t))c(c(t)))"; - // LayerID 0 1 2 3 4 5 6 - root = CreateLayerTree(layerTreeSyntax, nullptr, nullptr, lm, layers); - SetScrollableFrameMetrics(layers[0], FrameMetrics::START_SCROLL_ID); - SetScrollableFrameMetrics(layers[2], FrameMetrics::START_SCROLL_ID + 1); - SetScrollableFrameMetrics(layers[5], FrameMetrics::START_SCROLL_ID + 1); - SetScrollableFrameMetrics(layers[3], FrameMetrics::START_SCROLL_ID + 2); - SetScrollableFrameMetrics(layers[6], FrameMetrics::START_SCROLL_ID + 3); - } - - void CreateBug1194876Tree() { - const char* layerTreeSyntax = "c(t)"; - // LayerID 0 1 - nsIntRegion layerVisibleRegion[] = { - nsIntRegion(IntRect(0,0,100,100)), - nsIntRegion(IntRect(0,0,100,100)), - }; - root = CreateLayerTree(layerTreeSyntax, layerVisibleRegion, nullptr, lm, layers); - SetScrollableFrameMetrics(layers[0], FrameMetrics::START_SCROLL_ID); - SetScrollableFrameMetrics(layers[1], FrameMetrics::START_SCROLL_ID + 1); - - // Make layers[1] the root content - FrameMetrics childMetrics = layers[1]->GetFrameMetrics(0); - childMetrics.SetIsRootContent(true); - layers[1]->SetFrameMetrics(childMetrics); - - // Both layers are fully dispatch-to-content - EventRegions regions; - regions.mHitRegion = nsIntRegion(IntRect(0, 0, 100, 100)); - regions.mDispatchToContentHitRegion = regions.mHitRegion; - layers[0]->SetEventRegions(regions); - layers[1]->SetEventRegions(regions); - } -}; - -class APZHitTestingTester : public APZCTreeManagerTester { -protected: - ScreenToParentLayerMatrix4x4 transformToApzc; - ParentLayerToScreenMatrix4x4 transformToGecko; - - already_AddRefed<AsyncPanZoomController> GetTargetAPZC(const ScreenPoint& aPoint) { - RefPtr<AsyncPanZoomController> hit = manager->GetTargetAPZC(aPoint, nullptr); - if (hit) { - transformToApzc = manager->GetScreenToApzcTransform(hit.get()); - transformToGecko = manager->GetApzcToGeckoTransform(hit.get()); - } - return hit.forget(); - } - -protected: - void CreateHitTesting1LayerTree() { - const char* layerTreeSyntax = "c(tttt)"; - // LayerID 0 1234 - nsIntRegion layerVisibleRegion[] = { - nsIntRegion(IntRect(0,0,100,100)), - nsIntRegion(IntRect(0,0,100,100)), - nsIntRegion(IntRect(10,10,20,20)), - nsIntRegion(IntRect(10,10,20,20)), - nsIntRegion(IntRect(5,5,20,20)), - }; - root = CreateLayerTree(layerTreeSyntax, layerVisibleRegion, nullptr, lm, layers); - } - - void CreateHitTesting2LayerTree() { - const char* layerTreeSyntax = "c(tc(t))"; - // LayerID 0 12 3 - nsIntRegion layerVisibleRegion[] = { - nsIntRegion(IntRect(0,0,100,100)), - nsIntRegion(IntRect(10,10,40,40)), - nsIntRegion(IntRect(10,60,40,40)), - nsIntRegion(IntRect(10,60,40,40)), - }; - Matrix4x4 transforms[] = { - Matrix4x4(), - Matrix4x4(), - Matrix4x4::Scaling(2, 1, 1), - Matrix4x4(), - }; - root = CreateLayerTree(layerTreeSyntax, layerVisibleRegion, transforms, lm, layers); - - SetScrollableFrameMetrics(root, FrameMetrics::START_SCROLL_ID, CSSRect(0, 0, 200, 200)); - SetScrollableFrameMetrics(layers[1], FrameMetrics::START_SCROLL_ID + 1, CSSRect(0, 0, 80, 80)); - SetScrollableFrameMetrics(layers[3], FrameMetrics::START_SCROLL_ID + 2, CSSRect(0, 0, 80, 80)); - } - - void CreateComplexMultiLayerTree() { - const char* layerTreeSyntax = "c(tc(t)tc(c(t)tt))"; - // LayerID 0 12 3 45 6 7 89 - nsIntRegion layerVisibleRegion[] = { - nsIntRegion(IntRect(0,0,300,400)), // root(0) - nsIntRegion(IntRect(0,0,100,100)), // thebes(1) in top-left - nsIntRegion(IntRect(50,50,200,300)), // container(2) centered in root(0) - nsIntRegion(IntRect(50,50,200,300)), // thebes(3) fully occupying parent container(2) - nsIntRegion(IntRect(0,200,100,100)), // thebes(4) in bottom-left - nsIntRegion(IntRect(200,0,100,400)), // container(5) along the right 100px of root(0) - nsIntRegion(IntRect(200,0,100,200)), // container(6) taking up the top half of parent container(5) - nsIntRegion(IntRect(200,0,100,200)), // thebes(7) fully occupying parent container(6) - nsIntRegion(IntRect(200,200,100,100)), // thebes(8) in bottom-right (below (6)) - nsIntRegion(IntRect(200,300,100,100)), // thebes(9) in bottom-right (below (8)) - }; - root = CreateLayerTree(layerTreeSyntax, layerVisibleRegion, nullptr, lm, layers); - SetScrollableFrameMetrics(layers[1], FrameMetrics::START_SCROLL_ID); - SetScrollableFrameMetrics(layers[2], FrameMetrics::START_SCROLL_ID); - SetScrollableFrameMetrics(layers[4], FrameMetrics::START_SCROLL_ID + 1); - SetScrollableFrameMetrics(layers[6], FrameMetrics::START_SCROLL_ID + 1); - SetScrollableFrameMetrics(layers[7], FrameMetrics::START_SCROLL_ID + 2); - SetScrollableFrameMetrics(layers[8], FrameMetrics::START_SCROLL_ID + 1); - SetScrollableFrameMetrics(layers[9], FrameMetrics::START_SCROLL_ID + 3); - } - - void CreateBug1148350LayerTree() { - const char* layerTreeSyntax = "c(t)"; - // LayerID 0 1 - nsIntRegion layerVisibleRegion[] = { - nsIntRegion(IntRect(0,0,200,200)), - nsIntRegion(IntRect(0,0,200,200)), - }; - root = CreateLayerTree(layerTreeSyntax, layerVisibleRegion, nullptr, lm, layers); - SetScrollableFrameMetrics(layers[1], FrameMetrics::START_SCROLL_ID); - } -}; - -// A simple hit testing test that doesn't involve any transforms on layers. -TEST_F(APZHitTestingTester, HitTesting1) { - CreateHitTesting1LayerTree(); - ScopedLayerTreeRegistration registration(manager, 0, root, mcc); - - // No APZC attached so hit testing will return no APZC at (20,20) - RefPtr<AsyncPanZoomController> hit = GetTargetAPZC(ScreenPoint(20, 20)); - TestAsyncPanZoomController* nullAPZC = nullptr; - EXPECT_EQ(nullAPZC, hit.get()); - EXPECT_EQ(ScreenToParentLayerMatrix4x4(), transformToApzc); - EXPECT_EQ(ParentLayerToScreenMatrix4x4(), transformToGecko); - - uint32_t paintSequenceNumber = 0; - - // Now we have a root APZC that will match the page - SetScrollableFrameMetrics(root, FrameMetrics::START_SCROLL_ID); - manager->UpdateHitTestingTree(nullptr, root, false, 0, paintSequenceNumber++); - hit = GetTargetAPZC(ScreenPoint(15, 15)); - EXPECT_EQ(ApzcOf(root), hit.get()); - // expect hit point at LayerIntPoint(15, 15) - EXPECT_EQ(ParentLayerPoint(15, 15), transformToApzc * ScreenPoint(15, 15)); - EXPECT_EQ(ScreenPoint(15, 15), transformToGecko * ParentLayerPoint(15, 15)); - - // Now we have a sub APZC with a better fit - SetScrollableFrameMetrics(layers[3], FrameMetrics::START_SCROLL_ID + 1); - manager->UpdateHitTestingTree(nullptr, root, false, 0, paintSequenceNumber++); - EXPECT_NE(ApzcOf(root), ApzcOf(layers[3])); - hit = GetTargetAPZC(ScreenPoint(25, 25)); - EXPECT_EQ(ApzcOf(layers[3]), hit.get()); - // expect hit point at LayerIntPoint(25, 25) - EXPECT_EQ(ParentLayerPoint(25, 25), transformToApzc * ScreenPoint(25, 25)); - EXPECT_EQ(ScreenPoint(25, 25), transformToGecko * ParentLayerPoint(25, 25)); - - // At this point, layers[4] obscures layers[3] at the point (15, 15) so - // hitting there should hit the root APZC - hit = GetTargetAPZC(ScreenPoint(15, 15)); - EXPECT_EQ(ApzcOf(root), hit.get()); - - // Now test hit testing when we have two scrollable layers - SetScrollableFrameMetrics(layers[4], FrameMetrics::START_SCROLL_ID + 2); - manager->UpdateHitTestingTree(nullptr, root, false, 0, paintSequenceNumber++); - hit = GetTargetAPZC(ScreenPoint(15, 15)); - EXPECT_EQ(ApzcOf(layers[4]), hit.get()); - // expect hit point at LayerIntPoint(15, 15) - EXPECT_EQ(ParentLayerPoint(15, 15), transformToApzc * ScreenPoint(15, 15)); - EXPECT_EQ(ScreenPoint(15, 15), transformToGecko * ParentLayerPoint(15, 15)); - - // Hit test ouside the reach of layer[3,4] but inside root - hit = GetTargetAPZC(ScreenPoint(90, 90)); - EXPECT_EQ(ApzcOf(root), hit.get()); - // expect hit point at LayerIntPoint(90, 90) - EXPECT_EQ(ParentLayerPoint(90, 90), transformToApzc * ScreenPoint(90, 90)); - EXPECT_EQ(ScreenPoint(90, 90), transformToGecko * ParentLayerPoint(90, 90)); - - // Hit test ouside the reach of any layer - hit = GetTargetAPZC(ScreenPoint(1000, 10)); - EXPECT_EQ(nullAPZC, hit.get()); - EXPECT_EQ(ScreenToParentLayerMatrix4x4(), transformToApzc); - EXPECT_EQ(ParentLayerToScreenMatrix4x4(), transformToGecko); - hit = GetTargetAPZC(ScreenPoint(-1000, 10)); - EXPECT_EQ(nullAPZC, hit.get()); - EXPECT_EQ(ScreenToParentLayerMatrix4x4(), transformToApzc); - EXPECT_EQ(ParentLayerToScreenMatrix4x4(), transformToGecko); -} - -// A more involved hit testing test that involves css and async transforms. -TEST_F(APZHitTestingTester, HitTesting2) { - CreateHitTesting2LayerTree(); - ScopedLayerTreeRegistration registration(manager, 0, root, mcc); - - manager->UpdateHitTestingTree(nullptr, root, false, 0, 0); - - // At this point, the following holds (all coordinates in screen pixels): - // layers[0] has content from (0,0)-(200,200), clipped by composition bounds (0,0)-(100,100) - // layers[1] has content from (10,10)-(90,90), clipped by composition bounds (10,10)-(50,50) - // layers[2] has content from (20,60)-(100,100). no clipping as it's not a scrollable layer - // layers[3] has content from (20,60)-(180,140), clipped by composition bounds (20,60)-(100,100) - - TestAsyncPanZoomController* apzcroot = ApzcOf(root); - TestAsyncPanZoomController* apzc1 = ApzcOf(layers[1]); - TestAsyncPanZoomController* apzc3 = ApzcOf(layers[3]); - - // Hit an area that's clearly on the root layer but not any of the child layers. - RefPtr<AsyncPanZoomController> hit = GetTargetAPZC(ScreenPoint(75, 25)); - EXPECT_EQ(apzcroot, hit.get()); - EXPECT_EQ(ParentLayerPoint(75, 25), transformToApzc * ScreenPoint(75, 25)); - EXPECT_EQ(ScreenPoint(75, 25), transformToGecko * ParentLayerPoint(75, 25)); - - // Hit an area on the root that would be on layers[3] if layers[2] - // weren't transformed. - // Note that if layers[2] were scrollable, then this would hit layers[2] - // because its composition bounds would be at (10,60)-(50,100) (and the - // scale-only transform that we set on layers[2] would be invalid because - // it would place the layer into overscroll, as its composition bounds - // start at x=10 but its content at x=20). - hit = GetTargetAPZC(ScreenPoint(15, 75)); - EXPECT_EQ(apzcroot, hit.get()); - EXPECT_EQ(ParentLayerPoint(15, 75), transformToApzc * ScreenPoint(15, 75)); - EXPECT_EQ(ScreenPoint(15, 75), transformToGecko * ParentLayerPoint(15, 75)); - - // Hit an area on layers[1]. - hit = GetTargetAPZC(ScreenPoint(25, 25)); - EXPECT_EQ(apzc1, hit.get()); - EXPECT_EQ(ParentLayerPoint(25, 25), transformToApzc * ScreenPoint(25, 25)); - EXPECT_EQ(ScreenPoint(25, 25), transformToGecko * ParentLayerPoint(25, 25)); - - // Hit an area on layers[3]. - hit = GetTargetAPZC(ScreenPoint(25, 75)); - EXPECT_EQ(apzc3, hit.get()); - // transformToApzc should unapply layers[2]'s transform - EXPECT_EQ(ParentLayerPoint(12.5, 75), transformToApzc * ScreenPoint(25, 75)); - // and transformToGecko should reapply it - EXPECT_EQ(ScreenPoint(25, 75), transformToGecko * ParentLayerPoint(12.5, 75)); - - // Hit an area on layers[3] that would be on the root if layers[2] - // weren't transformed. - hit = GetTargetAPZC(ScreenPoint(75, 75)); - EXPECT_EQ(apzc3, hit.get()); - // transformToApzc should unapply layers[2]'s transform - EXPECT_EQ(ParentLayerPoint(37.5, 75), transformToApzc * ScreenPoint(75, 75)); - // and transformToGecko should reapply it - EXPECT_EQ(ScreenPoint(75, 75), transformToGecko * ParentLayerPoint(37.5, 75)); - - // Pan the root layer upward by 50 pixels. - // This causes layers[1] to scroll out of view, and an async transform - // of -50 to be set on the root layer. - EXPECT_CALL(*mcc, RequestContentRepaint(_)).Times(1); - - // This first pan will move the APZC by 50 pixels, and dispatch a paint request. - // Since this paint request is in the queue to Gecko, transformToGecko will - // take it into account. - ApzcPanNoFling(apzcroot, mcc, 100, 50); - - // Hit where layers[3] used to be. It should now hit the root. - hit = GetTargetAPZC(ScreenPoint(75, 75)); - EXPECT_EQ(apzcroot, hit.get()); - // transformToApzc doesn't unapply the root's own async transform - EXPECT_EQ(ParentLayerPoint(75, 75), transformToApzc * ScreenPoint(75, 75)); - // and transformToGecko unapplies it and then reapplies it, because by the - // time the event being transformed reaches Gecko the new paint request will - // have been handled. - EXPECT_EQ(ScreenPoint(75, 75), transformToGecko * ParentLayerPoint(75, 75)); - - // Hit where layers[1] used to be and where layers[3] should now be. - hit = GetTargetAPZC(ScreenPoint(25, 25)); - EXPECT_EQ(apzc3, hit.get()); - // transformToApzc unapplies both layers[2]'s css transform and the root's - // async transform - EXPECT_EQ(ParentLayerPoint(12.5, 75), transformToApzc * ScreenPoint(25, 25)); - // transformToGecko reapplies both the css transform and the async transform - // because we have already issued a paint request with it. - EXPECT_EQ(ScreenPoint(25, 25), transformToGecko * ParentLayerPoint(12.5, 75)); - - // This second pan will move the APZC by another 50 pixels. - EXPECT_CALL(*mcc, RequestContentRepaint(_)).Times(1); - ApzcPanNoFling(apzcroot, mcc, 100, 50); - - // Hit where layers[3] used to be. It should now hit the root. - hit = GetTargetAPZC(ScreenPoint(75, 75)); - EXPECT_EQ(apzcroot, hit.get()); - // transformToApzc doesn't unapply the root's own async transform - EXPECT_EQ(ParentLayerPoint(75, 75), transformToApzc * ScreenPoint(75, 75)); - // transformToGecko unapplies the full async transform of -100 pixels - EXPECT_EQ(ScreenPoint(75, 75), transformToGecko * ParentLayerPoint(75, 75)); - - // Hit where layers[1] used to be. It should now hit the root. - hit = GetTargetAPZC(ScreenPoint(25, 25)); - EXPECT_EQ(apzcroot, hit.get()); - // transformToApzc doesn't unapply the root's own async transform - EXPECT_EQ(ParentLayerPoint(25, 25), transformToApzc * ScreenPoint(25, 25)); - // transformToGecko unapplies the full async transform of -100 pixels - EXPECT_EQ(ScreenPoint(25, 25), transformToGecko * ParentLayerPoint(25, 25)); -} - -TEST_F(APZCTreeManagerTester, ScrollablePaintedLayers) { - CreateSimpleMultiLayerTree(); - ScopedLayerTreeRegistration registration(manager, 0, root, mcc); - - // both layers have the same scrollId - SetScrollableFrameMetrics(layers[1], FrameMetrics::START_SCROLL_ID); - SetScrollableFrameMetrics(layers[2], FrameMetrics::START_SCROLL_ID); - manager->UpdateHitTestingTree(nullptr, root, false, 0, 0); - - TestAsyncPanZoomController* nullAPZC = nullptr; - // so they should have the same APZC - EXPECT_FALSE(layers[0]->HasScrollableFrameMetrics()); - EXPECT_NE(nullAPZC, ApzcOf(layers[1])); - EXPECT_NE(nullAPZC, ApzcOf(layers[2])); - EXPECT_EQ(ApzcOf(layers[1]), ApzcOf(layers[2])); - - // Change the scrollId of layers[1], and verify the APZC changes - SetScrollableFrameMetrics(layers[1], FrameMetrics::START_SCROLL_ID + 1); - manager->UpdateHitTestingTree(nullptr, root, false, 0, 0); - EXPECT_NE(ApzcOf(layers[1]), ApzcOf(layers[2])); - - // Change the scrollId of layers[2] to match that of layers[1], ensure we get the same - // APZC for both again - SetScrollableFrameMetrics(layers[2], FrameMetrics::START_SCROLL_ID + 1); - manager->UpdateHitTestingTree(nullptr, root, false, 0, 0); - EXPECT_EQ(ApzcOf(layers[1]), ApzcOf(layers[2])); -} - -TEST_F(APZCTreeManagerTester, Bug1068268) { - CreatePotentiallyLeakingTree(); - ScopedLayerTreeRegistration registration(manager, 0, root, mcc); - - manager->UpdateHitTestingTree(nullptr, root, false, 0, 0); - RefPtr<HitTestingTreeNode> root = manager->GetRootNode(); - RefPtr<HitTestingTreeNode> node2 = root->GetFirstChild()->GetFirstChild(); - RefPtr<HitTestingTreeNode> node5 = root->GetLastChild()->GetLastChild(); - - EXPECT_EQ(ApzcOf(layers[2]), node5->GetApzc()); - EXPECT_EQ(ApzcOf(layers[2]), node2->GetApzc()); - EXPECT_EQ(ApzcOf(layers[0]), ApzcOf(layers[2])->GetParent()); - EXPECT_EQ(ApzcOf(layers[2]), ApzcOf(layers[5])); - - EXPECT_EQ(node2->GetFirstChild(), node2->GetLastChild()); - EXPECT_EQ(ApzcOf(layers[3]), node2->GetLastChild()->GetApzc()); - EXPECT_EQ(node5->GetFirstChild(), node5->GetLastChild()); - EXPECT_EQ(ApzcOf(layers[6]), node5->GetLastChild()->GetApzc()); - EXPECT_EQ(ApzcOf(layers[2]), ApzcOf(layers[3])->GetParent()); - EXPECT_EQ(ApzcOf(layers[5]), ApzcOf(layers[6])->GetParent()); -} - -TEST_F(APZCTreeManagerTester, Bug1194876) { - CreateBug1194876Tree(); - ScopedLayerTreeRegistration registration(manager, 0, root, mcc); - manager->UpdateHitTestingTree(nullptr, root, false, 0, 0); - - uint64_t blockId; - nsTArray<ScrollableLayerGuid> targets; - - // First touch goes down, APZCTM will hit layers[1] because it is on top of - // layers[0], but we tell it the real target APZC is layers[0]. - MultiTouchInput mti; - mti = CreateMultiTouchInput(MultiTouchInput::MULTITOUCH_START, mcc->Time()); - mti.mTouches.AppendElement(SingleTouchData(0, ParentLayerPoint(25, 50), ScreenSize(0, 0), 0, 0)); - manager->ReceiveInputEvent(mti, nullptr, &blockId); - manager->ContentReceivedInputBlock(blockId, false); - targets.AppendElement(ApzcOf(layers[0])->GetGuid()); - manager->SetTargetAPZC(blockId, targets); - - // Around here, the above touch will get processed by ApzcOf(layers[0]) - - // Second touch goes down (first touch remains down), APZCTM will again hit - // layers[1]. Again we tell it both touches landed on layers[0], but because - // layers[1] is the RCD layer, it will end up being the multitouch target. - mti.mTouches.AppendElement(SingleTouchData(1, ParentLayerPoint(75, 50), ScreenSize(0, 0), 0, 0)); - manager->ReceiveInputEvent(mti, nullptr, &blockId); - manager->ContentReceivedInputBlock(blockId, false); - targets.AppendElement(ApzcOf(layers[0])->GetGuid()); - manager->SetTargetAPZC(blockId, targets); - - // Around here, the above multi-touch will get processed by ApzcOf(layers[1]). - // We want to ensure that ApzcOf(layers[0]) has had its state cleared, because - // otherwise it will do things like dispatch spurious long-tap events. - - EXPECT_CALL(*mcc, HandleLongTap(_, _, _, _)).Times(0); -} - -TEST_F(APZCTreeManagerTester, Bug1198900) { - // This is just a test that cancels a wheel event to make sure it doesn't - // crash. - CreateSimpleDTCScrollingLayer(); - ScopedLayerTreeRegistration registration(manager, 0, root, mcc); - manager->UpdateHitTestingTree(nullptr, root, false, 0, 0); - - ScreenPoint origin(100, 50); - ScrollWheelInput swi(MillisecondsSinceStartup(mcc->Time()), mcc->Time(), 0, - ScrollWheelInput::SCROLLMODE_INSTANT, ScrollWheelInput::SCROLLDELTA_PIXEL, - origin, 0, 10); - uint64_t blockId; - manager->ReceiveInputEvent(swi, nullptr, &blockId); - manager->ContentReceivedInputBlock(blockId, /* preventDefault= */ true); -} - -TEST_F(APZHitTestingTester, ComplexMultiLayerTree) { - CreateComplexMultiLayerTree(); - ScopedLayerTreeRegistration registration(manager, 0, root, mcc); - manager->UpdateHitTestingTree(nullptr, root, false, 0, 0); - - /* The layer tree looks like this: - - 0 - |----|--+--|----| - 1 2 4 5 - | /|\ - 3 6 8 9 - | - 7 - - Layers 1,2 have the same APZC - Layers 4,6,8 have the same APZC - Layer 7 has an APZC - Layer 9 has an APZC - */ - - TestAsyncPanZoomController* nullAPZC = nullptr; - // Ensure all the scrollable layers have an APZC - EXPECT_FALSE(layers[0]->HasScrollableFrameMetrics()); - EXPECT_NE(nullAPZC, ApzcOf(layers[1])); - EXPECT_NE(nullAPZC, ApzcOf(layers[2])); - EXPECT_FALSE(layers[3]->HasScrollableFrameMetrics()); - EXPECT_NE(nullAPZC, ApzcOf(layers[4])); - EXPECT_FALSE(layers[5]->HasScrollableFrameMetrics()); - EXPECT_NE(nullAPZC, ApzcOf(layers[6])); - EXPECT_NE(nullAPZC, ApzcOf(layers[7])); - EXPECT_NE(nullAPZC, ApzcOf(layers[8])); - EXPECT_NE(nullAPZC, ApzcOf(layers[9])); - // Ensure those that scroll together have the same APZCs - EXPECT_EQ(ApzcOf(layers[1]), ApzcOf(layers[2])); - EXPECT_EQ(ApzcOf(layers[4]), ApzcOf(layers[6])); - EXPECT_EQ(ApzcOf(layers[8]), ApzcOf(layers[6])); - // Ensure those that don't scroll together have different APZCs - EXPECT_NE(ApzcOf(layers[1]), ApzcOf(layers[4])); - EXPECT_NE(ApzcOf(layers[1]), ApzcOf(layers[7])); - EXPECT_NE(ApzcOf(layers[1]), ApzcOf(layers[9])); - EXPECT_NE(ApzcOf(layers[4]), ApzcOf(layers[7])); - EXPECT_NE(ApzcOf(layers[4]), ApzcOf(layers[9])); - EXPECT_NE(ApzcOf(layers[7]), ApzcOf(layers[9])); - // Ensure the APZC parent chains are set up correctly - TestAsyncPanZoomController* layers1_2 = ApzcOf(layers[1]); - TestAsyncPanZoomController* layers4_6_8 = ApzcOf(layers[4]); - TestAsyncPanZoomController* layer7 = ApzcOf(layers[7]); - TestAsyncPanZoomController* layer9 = ApzcOf(layers[9]); - EXPECT_EQ(nullptr, layers1_2->GetParent()); - EXPECT_EQ(nullptr, layers4_6_8->GetParent()); - EXPECT_EQ(layers4_6_8, layer7->GetParent()); - EXPECT_EQ(nullptr, layer9->GetParent()); - // Ensure the hit-testing tree looks like the layer tree - RefPtr<HitTestingTreeNode> root = manager->GetRootNode(); - RefPtr<HitTestingTreeNode> node5 = root->GetLastChild(); - RefPtr<HitTestingTreeNode> node4 = node5->GetPrevSibling(); - RefPtr<HitTestingTreeNode> node2 = node4->GetPrevSibling(); - RefPtr<HitTestingTreeNode> node1 = node2->GetPrevSibling(); - RefPtr<HitTestingTreeNode> node3 = node2->GetLastChild(); - RefPtr<HitTestingTreeNode> node9 = node5->GetLastChild(); - RefPtr<HitTestingTreeNode> node8 = node9->GetPrevSibling(); - RefPtr<HitTestingTreeNode> node6 = node8->GetPrevSibling(); - RefPtr<HitTestingTreeNode> node7 = node6->GetLastChild(); - EXPECT_EQ(nullptr, node1->GetPrevSibling()); - EXPECT_EQ(nullptr, node3->GetPrevSibling()); - EXPECT_EQ(nullptr, node6->GetPrevSibling()); - EXPECT_EQ(nullptr, node7->GetPrevSibling()); - EXPECT_EQ(nullptr, node1->GetLastChild()); - EXPECT_EQ(nullptr, node3->GetLastChild()); - EXPECT_EQ(nullptr, node4->GetLastChild()); - EXPECT_EQ(nullptr, node7->GetLastChild()); - EXPECT_EQ(nullptr, node8->GetLastChild()); - EXPECT_EQ(nullptr, node9->GetLastChild()); - - RefPtr<AsyncPanZoomController> hit = GetTargetAPZC(ScreenPoint(25, 25)); - EXPECT_EQ(ApzcOf(layers[1]), hit.get()); - hit = GetTargetAPZC(ScreenPoint(275, 375)); - EXPECT_EQ(ApzcOf(layers[9]), hit.get()); - hit = GetTargetAPZC(ScreenPoint(250, 100)); - EXPECT_EQ(ApzcOf(layers[7]), hit.get()); -} - -TEST_F(APZHitTestingTester, TestRepaintFlushOnNewInputBlock) { - SCOPED_GFX_PREF(TouchActionEnabled, bool, false); - - // The main purpose of this test is to verify that touch-start events (or anything - // that starts a new input block) don't ever get untransformed. This should always - // hold because the APZ code should flush repaints when we start a new input block - // and the transform to gecko space should be empty. - - CreateSimpleScrollingLayer(); - ScopedLayerTreeRegistration registration(manager, 0, root, mcc); - manager->UpdateHitTestingTree(nullptr, root, false, 0, 0); - TestAsyncPanZoomController* apzcroot = ApzcOf(root); - - // At this point, the following holds (all coordinates in screen pixels): - // layers[0] has content from (0,0)-(500,500), clipped by composition bounds (0,0)-(200,200) - - MockFunction<void(std::string checkPointName)> check; - - { - InSequence s; - - EXPECT_CALL(*mcc, RequestContentRepaint(_)).Times(AtLeast(1)); - EXPECT_CALL(check, Call("post-first-touch-start")); - EXPECT_CALL(*mcc, RequestContentRepaint(_)).Times(AtLeast(1)); - EXPECT_CALL(check, Call("post-second-fling")); - EXPECT_CALL(*mcc, RequestContentRepaint(_)).Times(AtLeast(1)); - EXPECT_CALL(check, Call("post-second-touch-start")); - } - - // This first pan will move the APZC by 50 pixels, and dispatch a paint request. - ApzcPanNoFling(apzcroot, mcc, 100, 50); - - // Verify that a touch start doesn't get untransformed - ScreenIntPoint touchPoint(50, 50); - MultiTouchInput mti = CreateMultiTouchInput(MultiTouchInput::MULTITOUCH_START, mcc->Time()); - mti.mTouches.AppendElement(SingleTouchData(0, touchPoint, ScreenSize(0, 0), 0, 0)); - - EXPECT_EQ(nsEventStatus_eConsumeDoDefault, manager->ReceiveInputEvent(mti, nullptr, nullptr)); - EXPECT_EQ(touchPoint, mti.mTouches[0].mScreenPoint); - check.Call("post-first-touch-start"); - - // Send a touchend to clear state - mti.mType = MultiTouchInput::MULTITOUCH_END; - manager->ReceiveInputEvent(mti, nullptr, nullptr); - - mcc->AdvanceByMillis(1000); - - // Now do two pans. The first of these will dispatch a repaint request, as above. - // The second will get stuck in the paint throttler because the first one doesn't - // get marked as "completed", so this will result in a non-empty LD transform. - // (Note that any outstanding repaint requests from the first half of this test - // don't impact this half because we advance the time by 1 second, which will trigger - // the max-wait-exceeded codepath in the paint throttler). - ApzcPanNoFling(apzcroot, mcc, 100, 50); - check.Call("post-second-fling"); - ApzcPanNoFling(apzcroot, mcc, 100, 50); - - // Ensure that a touch start again doesn't get untransformed by flushing - // a repaint - mti.mType = MultiTouchInput::MULTITOUCH_START; - EXPECT_EQ(nsEventStatus_eConsumeDoDefault, manager->ReceiveInputEvent(mti, nullptr, nullptr)); - EXPECT_EQ(touchPoint, mti.mTouches[0].mScreenPoint); - check.Call("post-second-touch-start"); - - mti.mType = MultiTouchInput::MULTITOUCH_END; - EXPECT_EQ(nsEventStatus_eConsumeDoDefault, manager->ReceiveInputEvent(mti, nullptr, nullptr)); - EXPECT_EQ(touchPoint, mti.mTouches[0].mScreenPoint); -} - -TEST_F(APZHitTestingTester, TestRepaintFlushOnWheelEvents) { - // The purpose of this test is to ensure that wheel events trigger a repaint - // flush as per bug 1166871, and that the wheel event untransform is a no-op. - - CreateSimpleScrollingLayer(); - ScopedLayerTreeRegistration registration(manager, 0, root, mcc); - manager->UpdateHitTestingTree(nullptr, root, false, 0, 0); - TestAsyncPanZoomController* apzcroot = ApzcOf(root); - - EXPECT_CALL(*mcc, RequestContentRepaint(_)).Times(AtLeast(3)); - ScreenPoint origin(100, 50); - for (int i = 0; i < 3; i++) { - ScrollWheelInput swi(MillisecondsSinceStartup(mcc->Time()), mcc->Time(), 0, - ScrollWheelInput::SCROLLMODE_INSTANT, ScrollWheelInput::SCROLLDELTA_PIXEL, - origin, 0, 10); - EXPECT_EQ(nsEventStatus_eConsumeDoDefault, manager->ReceiveInputEvent(swi, nullptr, nullptr)); - EXPECT_EQ(origin, swi.mOrigin); - - AsyncTransform viewTransform; - ParentLayerPoint point; - apzcroot->SampleContentTransformForFrame(&viewTransform, point); - EXPECT_EQ(0, point.x); - EXPECT_EQ((i + 1) * 10, point.y); - EXPECT_EQ(0, viewTransform.mTranslation.x); - EXPECT_EQ((i + 1) * -10, viewTransform.mTranslation.y); - - mcc->AdvanceByMillis(5); - } -} - -TEST_F(APZHitTestingTester, Bug1148350) { - CreateBug1148350LayerTree(); - ScopedLayerTreeRegistration registration(manager, 0, root, mcc); - manager->UpdateHitTestingTree(nullptr, root, false, 0, 0); - - MockFunction<void(std::string checkPointName)> check; - { - InSequence s; - EXPECT_CALL(*mcc, HandleSingleTap(CSSPoint(100, 100), 0, ApzcOf(layers[1])->GetGuid())).Times(1); - EXPECT_CALL(check, Call("Tapped without transform")); - EXPECT_CALL(*mcc, HandleSingleTap(CSSPoint(100, 100), 0, ApzcOf(layers[1])->GetGuid())).Times(1); - EXPECT_CALL(check, Call("Tapped with interleaved transform")); - } - - Tap(manager, 100, 100, mcc, TimeDuration::FromMilliseconds(100)); - mcc->RunThroughDelayedTasks(); - check.Call("Tapped without transform"); - - uint64_t blockId; - TouchDown(manager, 100, 100, mcc->Time(), &blockId); - if (gfxPrefs::TouchActionEnabled()) { - SetDefaultAllowedTouchBehavior(manager, blockId); - } - mcc->AdvanceByMillis(100); - - layers[0]->SetVisibleRegion(LayerIntRegion(LayerIntRect(0,50,200,150))); - layers[0]->SetBaseTransform(Matrix4x4::Translation(0, 50, 0)); - manager->UpdateHitTestingTree(nullptr, root, false, 0, 0); - - TouchUp(manager, 100, 100, mcc->Time()); - mcc->RunThroughDelayedTasks(); - check.Call("Tapped with interleaved transform"); -} - -class APZOverscrollHandoffTester : public APZCTreeManagerTester { -protected: - UniquePtr<ScopedLayerTreeRegistration> registration; - TestAsyncPanZoomController* rootApzc; - - void CreateOverscrollHandoffLayerTree1() { - const char* layerTreeSyntax = "c(t)"; - nsIntRegion layerVisibleRegion[] = { - nsIntRegion(IntRect(0, 0, 100, 100)), - nsIntRegion(IntRect(0, 50, 100, 50)) - }; - root = CreateLayerTree(layerTreeSyntax, layerVisibleRegion, nullptr, lm, layers); - SetScrollableFrameMetrics(root, FrameMetrics::START_SCROLL_ID, CSSRect(0, 0, 200, 200)); - SetScrollableFrameMetrics(layers[1], FrameMetrics::START_SCROLL_ID + 1, CSSRect(0, 0, 100, 100)); - SetScrollHandoff(layers[1], root); - registration = MakeUnique<ScopedLayerTreeRegistration>(manager, 0, root, mcc); - manager->UpdateHitTestingTree(nullptr, root, false, 0, 0); - rootApzc = ApzcOf(root); - } - - void CreateOverscrollHandoffLayerTree2() { - const char* layerTreeSyntax = "c(c(t))"; - nsIntRegion layerVisibleRegion[] = { - nsIntRegion(IntRect(0, 0, 100, 100)), - nsIntRegion(IntRect(0, 0, 100, 100)), - nsIntRegion(IntRect(0, 50, 100, 50)) - }; - root = CreateLayerTree(layerTreeSyntax, layerVisibleRegion, nullptr, lm, layers); - SetScrollableFrameMetrics(root, FrameMetrics::START_SCROLL_ID, CSSRect(0, 0, 200, 200)); - SetScrollableFrameMetrics(layers[1], FrameMetrics::START_SCROLL_ID + 2, CSSRect(-100, -100, 200, 200)); - SetScrollableFrameMetrics(layers[2], FrameMetrics::START_SCROLL_ID + 1, CSSRect(0, 0, 100, 100)); - SetScrollHandoff(layers[1], root); - SetScrollHandoff(layers[2], layers[1]); - // No ScopedLayerTreeRegistration as that just needs to be done once per test - // and this is the second layer tree for a particular test. - MOZ_ASSERT(registration); - manager->UpdateHitTestingTree(nullptr, root, false, 0, 0); - rootApzc = ApzcOf(root); - } - - void CreateOverscrollHandoffLayerTree3() { - const char* layerTreeSyntax = "c(c(t)c(t))"; - nsIntRegion layerVisibleRegion[] = { - nsIntRegion(IntRect(0, 0, 100, 100)), // root - nsIntRegion(IntRect(0, 0, 100, 50)), // scrolling parent 1 - nsIntRegion(IntRect(0, 0, 100, 50)), // scrolling child 1 - nsIntRegion(IntRect(0, 50, 100, 50)), // scrolling parent 2 - nsIntRegion(IntRect(0, 50, 100, 50)) // scrolling child 2 - }; - root = CreateLayerTree(layerTreeSyntax, layerVisibleRegion, nullptr, lm, layers); - SetScrollableFrameMetrics(layers[1], FrameMetrics::START_SCROLL_ID, CSSRect(0, 0, 100, 100)); - SetScrollableFrameMetrics(layers[2], FrameMetrics::START_SCROLL_ID + 1, CSSRect(0, 0, 100, 100)); - SetScrollableFrameMetrics(layers[3], FrameMetrics::START_SCROLL_ID + 2, CSSRect(0, 50, 100, 100)); - SetScrollableFrameMetrics(layers[4], FrameMetrics::START_SCROLL_ID + 3, CSSRect(0, 50, 100, 100)); - SetScrollHandoff(layers[2], layers[1]); - SetScrollHandoff(layers[4], layers[3]); - registration = MakeUnique<ScopedLayerTreeRegistration>(manager, 0, root, mcc); - manager->UpdateHitTestingTree(nullptr, root, false, 0, 0); - } - - void CreateScrollgrabLayerTree(bool makeParentScrollable = true) { - const char* layerTreeSyntax = "c(t)"; - nsIntRegion layerVisibleRegion[] = { - nsIntRegion(IntRect(0, 0, 100, 100)), // scroll-grabbing parent - nsIntRegion(IntRect(0, 20, 100, 80)) // child - }; - root = CreateLayerTree(layerTreeSyntax, layerVisibleRegion, nullptr, lm, layers); - float parentHeight = makeParentScrollable ? 120 : 100; - SetScrollableFrameMetrics(root, FrameMetrics::START_SCROLL_ID, CSSRect(0, 0, 100, parentHeight)); - SetScrollableFrameMetrics(layers[1], FrameMetrics::START_SCROLL_ID + 1, CSSRect(0, 0, 100, 200)); - SetScrollHandoff(layers[1], root); - registration = MakeUnique<ScopedLayerTreeRegistration>(manager, 0, root, mcc); - manager->UpdateHitTestingTree(nullptr, root, false, 0, 0); - rootApzc = ApzcOf(root); - rootApzc->GetFrameMetrics().SetHasScrollgrab(true); - } - - void TestFlingAcceleration() { - // Jack up the fling acceleration multiplier so we can easily determine - // whether acceleration occured. - const float kAcceleration = 100.0f; - SCOPED_GFX_PREF(APZFlingAccelBaseMultiplier, float, kAcceleration); - - RefPtr<TestAsyncPanZoomController> childApzc = ApzcOf(layers[1]); - - // Pan once, enough to fully scroll the scrollgrab parent and then scroll - // and fling the child. - Pan(manager, mcc, 70, 40); - - // Give the fling animation a chance to start. - SampleAnimationsOnce(); - - float childVelocityAfterFling1 = childApzc->GetVelocityVector().y; - - // Pan again. - Pan(manager, mcc, 70, 40); - - // Give the fling animation a chance to start. - // This time it should be accelerated. - SampleAnimationsOnce(); - - float childVelocityAfterFling2 = childApzc->GetVelocityVector().y; - - // We should have accelerated once. - // The division by 2 is to account for friction. - EXPECT_GT(childVelocityAfterFling2, - childVelocityAfterFling1 * kAcceleration / 2); - - // We should not have accelerated twice. - // The division by 4 is to account for friction. - EXPECT_LE(childVelocityAfterFling2, - childVelocityAfterFling1 * kAcceleration * kAcceleration / 4); - } -}; - -// Here we test that if the processing of a touch block is deferred while we -// wait for content to send a prevent-default message, overscroll is still -// handed off correctly when the block is processed. -TEST_F(APZOverscrollHandoffTester, DeferredInputEventProcessing) { - // Set up the APZC tree. - CreateOverscrollHandoffLayerTree1(); - - TestAsyncPanZoomController* childApzc = ApzcOf(layers[1]); - - // Enable touch-listeners so that we can separate the queueing of input - // events from them being processed. - childApzc->SetWaitForMainThread(); - - // Queue input events for a pan. - uint64_t blockId = 0; - ApzcPanNoFling(childApzc, mcc, 90, 30, &blockId); - - // Allow the pan to be processed. - childApzc->ContentReceivedInputBlock(blockId, false); - childApzc->ConfirmTarget(blockId); - - // Make sure overscroll was handed off correctly. - EXPECT_EQ(50, childApzc->GetFrameMetrics().GetScrollOffset().y); - EXPECT_EQ(10, rootApzc->GetFrameMetrics().GetScrollOffset().y); -} - -// Here we test that if the layer structure changes in between two input -// blocks being queued, and the first block is only processed after the second -// one has been queued, overscroll handoff for the first block follows -// the original layer structure while overscroll handoff for the second block -// follows the new layer structure. -TEST_F(APZOverscrollHandoffTester, LayerStructureChangesWhileEventsArePending) { - // Set up an initial APZC tree. - CreateOverscrollHandoffLayerTree1(); - - TestAsyncPanZoomController* childApzc = ApzcOf(layers[1]); - - // Enable touch-listeners so that we can separate the queueing of input - // events from them being processed. - childApzc->SetWaitForMainThread(); - - // Queue input events for a pan. - uint64_t blockId = 0; - ApzcPanNoFling(childApzc, mcc, 90, 30, &blockId); - - // Modify the APZC tree to insert a new APZC 'middle' into the handoff chain - // between the child and the root. - CreateOverscrollHandoffLayerTree2(); - RefPtr<Layer> middle = layers[1]; - childApzc->SetWaitForMainThread(); - TestAsyncPanZoomController* middleApzc = ApzcOf(middle); - - // Queue input events for another pan. - uint64_t secondBlockId = 0; - ApzcPanNoFling(childApzc, mcc, 30, 90, &secondBlockId); - - // Allow the first pan to be processed. - childApzc->ContentReceivedInputBlock(blockId, false); - childApzc->ConfirmTarget(blockId); - - // Make sure things have scrolled according to the handoff chain in - // place at the time the touch-start of the first pan was queued. - EXPECT_EQ(50, childApzc->GetFrameMetrics().GetScrollOffset().y); - EXPECT_EQ(10, rootApzc->GetFrameMetrics().GetScrollOffset().y); - EXPECT_EQ(0, middleApzc->GetFrameMetrics().GetScrollOffset().y); - - // Allow the second pan to be processed. - childApzc->ContentReceivedInputBlock(secondBlockId, false); - childApzc->ConfirmTarget(secondBlockId); - - // Make sure things have scrolled according to the handoff chain in - // place at the time the touch-start of the second pan was queued. - EXPECT_EQ(0, childApzc->GetFrameMetrics().GetScrollOffset().y); - EXPECT_EQ(10, rootApzc->GetFrameMetrics().GetScrollOffset().y); - EXPECT_EQ(-10, middleApzc->GetFrameMetrics().GetScrollOffset().y); -} - -// Test that putting a second finger down on an APZC while a down-chain APZC -// is overscrolled doesn't result in being stuck in overscroll. -TEST_F(APZOverscrollHandoffTester, StuckInOverscroll_Bug1073250) { - // Enable overscrolling. - SCOPED_GFX_PREF(APZOverscrollEnabled, bool, true); - - CreateOverscrollHandoffLayerTree1(); - - TestAsyncPanZoomController* child = ApzcOf(layers[1]); - - // Pan, causing the parent APZC to overscroll. - Pan(manager, mcc, 10, 40, true /* keep finger down */); - EXPECT_FALSE(child->IsOverscrolled()); - EXPECT_TRUE(rootApzc->IsOverscrolled()); - - // Put a second finger down. - MultiTouchInput secondFingerDown(MultiTouchInput::MULTITOUCH_START, 0, TimeStamp(), 0); - // Use the same touch identifier for the first touch (0) as Pan(). (A bit hacky.) - secondFingerDown.mTouches.AppendElement(SingleTouchData(0, ScreenIntPoint(10, 40), ScreenSize(0, 0), 0, 0)); - secondFingerDown.mTouches.AppendElement(SingleTouchData(1, ScreenIntPoint(30, 20), ScreenSize(0, 0), 0, 0)); - manager->ReceiveInputEvent(secondFingerDown, nullptr, nullptr); - - // Release the fingers. - MultiTouchInput fingersUp = secondFingerDown; - fingersUp.mType = MultiTouchInput::MULTITOUCH_END; - manager->ReceiveInputEvent(fingersUp, nullptr, nullptr); - - // Allow any animations to run their course. - child->AdvanceAnimationsUntilEnd(); - rootApzc->AdvanceAnimationsUntilEnd(); - - // Make sure nothing is overscrolled. - EXPECT_FALSE(child->IsOverscrolled()); - EXPECT_FALSE(rootApzc->IsOverscrolled()); -} - -// This is almost exactly like StuckInOverscroll_Bug1073250, except the -// APZC receiving the input events for the first touch block is the child -// (and thus not the same APZC that overscrolls, which is the parent). -TEST_F(APZOverscrollHandoffTester, StuckInOverscroll_Bug1231228) { - // Enable overscrolling. - SCOPED_GFX_PREF(APZOverscrollEnabled, bool, true); - - CreateOverscrollHandoffLayerTree1(); - - TestAsyncPanZoomController* child = ApzcOf(layers[1]); - - // Pan, causing the parent APZC to overscroll. - Pan(manager, mcc, 60, 90, true /* keep finger down */); - EXPECT_FALSE(child->IsOverscrolled()); - EXPECT_TRUE(rootApzc->IsOverscrolled()); - - // Put a second finger down. - MultiTouchInput secondFingerDown(MultiTouchInput::MULTITOUCH_START, 0, TimeStamp(), 0); - // Use the same touch identifier for the first touch (0) as Pan(). (A bit hacky.) - secondFingerDown.mTouches.AppendElement(SingleTouchData(0, ScreenIntPoint(10, 40), ScreenSize(0, 0), 0, 0)); - secondFingerDown.mTouches.AppendElement(SingleTouchData(1, ScreenIntPoint(30, 20), ScreenSize(0, 0), 0, 0)); - manager->ReceiveInputEvent(secondFingerDown, nullptr, nullptr); - - // Release the fingers. - MultiTouchInput fingersUp = secondFingerDown; - fingersUp.mType = MultiTouchInput::MULTITOUCH_END; - manager->ReceiveInputEvent(fingersUp, nullptr, nullptr); - - // Allow any animations to run their course. - child->AdvanceAnimationsUntilEnd(); - rootApzc->AdvanceAnimationsUntilEnd(); - - // Make sure nothing is overscrolled. - EXPECT_FALSE(child->IsOverscrolled()); - EXPECT_FALSE(rootApzc->IsOverscrolled()); -} - -// Test that flinging in a direction where one component of the fling goes into -// overscroll but the other doesn't, results in just the one component being -// handed off to the parent, while the original APZC continues flinging in the -// other direction. -TEST_F(APZOverscrollHandoffTester, PartialFlingHandoff) { - CreateOverscrollHandoffLayerTree1(); - - // Fling up and to the left. The child APZC has room to scroll up, but not - // to the left, so the horizontal component of the fling should be handed - // off to the parent APZC. - Pan(manager, mcc, ScreenPoint(90, 90), ScreenPoint(55, 55)); - - RefPtr<TestAsyncPanZoomController> parent = ApzcOf(root); - RefPtr<TestAsyncPanZoomController> child = ApzcOf(layers[1]); - - // Advance the child's fling animation once to give the partial handoff - // a chance to occur. - mcc->AdvanceByMillis(10); - child->AdvanceAnimations(mcc->Time()); - - // Assert that partial handoff has occurred. - child->AssertStateIsFling(); - parent->AssertStateIsFling(); -} - -// Here we test that if two flings are happening simultaneously, overscroll -// is handed off correctly for each. -TEST_F(APZOverscrollHandoffTester, SimultaneousFlings) { - // Set up an initial APZC tree. - CreateOverscrollHandoffLayerTree3(); - - RefPtr<TestAsyncPanZoomController> parent1 = ApzcOf(layers[1]); - RefPtr<TestAsyncPanZoomController> child1 = ApzcOf(layers[2]); - RefPtr<TestAsyncPanZoomController> parent2 = ApzcOf(layers[3]); - RefPtr<TestAsyncPanZoomController> child2 = ApzcOf(layers[4]); - - // Pan on the lower child. - Pan(child2, mcc, 45, 5); - - // Pan on the upper child. - Pan(child1, mcc, 95, 55); - - // Check that child1 and child2 are in a FLING state. - child1->AssertStateIsFling(); - child2->AssertStateIsFling(); - - // Advance the animations on child1 and child2 until their end. - child1->AdvanceAnimationsUntilEnd(); - child2->AdvanceAnimationsUntilEnd(); - - // Check that the flings have been handed off to the parents. - child1->AssertStateIsReset(); - parent1->AssertStateIsFling(); - child2->AssertStateIsReset(); - parent2->AssertStateIsFling(); -} - -TEST_F(APZOverscrollHandoffTester, Scrollgrab) { - // Set up the layer tree - CreateScrollgrabLayerTree(); - - RefPtr<TestAsyncPanZoomController> childApzc = ApzcOf(layers[1]); - - // Pan on the child, enough to fully scroll the scrollgrab parent (20 px) - // and leave some more (another 15 px) for the child. - Pan(childApzc, mcc, 80, 45); - - // Check that the parent and child have scrolled as much as we expect. - EXPECT_EQ(20, rootApzc->GetFrameMetrics().GetScrollOffset().y); - EXPECT_EQ(15, childApzc->GetFrameMetrics().GetScrollOffset().y); -} - -TEST_F(APZOverscrollHandoffTester, ScrollgrabFling) { - // Set up the layer tree - CreateScrollgrabLayerTree(); - - RefPtr<TestAsyncPanZoomController> childApzc = ApzcOf(layers[1]); - - // Pan on the child, not enough to fully scroll the scrollgrab parent. - Pan(childApzc, mcc, 80, 70); - - // Check that it is the scrollgrab parent that's in a fling, not the child. - rootApzc->AssertStateIsFling(); - childApzc->AssertStateIsReset(); -} - -TEST_F(APZOverscrollHandoffTester, ScrollgrabFlingAcceleration1) { - CreateScrollgrabLayerTree(true /* make parent scrollable */); - TestFlingAcceleration(); -} - -TEST_F(APZOverscrollHandoffTester, ScrollgrabFlingAcceleration2) { - CreateScrollgrabLayerTree(false /* do not make parent scrollable */); - TestFlingAcceleration(); -} - -TEST_F(APZOverscrollHandoffTester, ImmediateHandoffDisallowed_Pan) { - SCOPED_GFX_PREF(APZAllowImmediateHandoff, bool, false); - - CreateOverscrollHandoffLayerTree1(); - - RefPtr<TestAsyncPanZoomController> parentApzc = ApzcOf(root); - RefPtr<TestAsyncPanZoomController> childApzc = ApzcOf(layers[1]); - - // Pan on the child, enough to scroll it to its end and have scroll - // left to hand off. Since immediate handoff is disallowed, we expect - // the leftover scroll not to be handed off. - Pan(childApzc, mcc, 60, 5); - - // Verify that the parent has not scrolled. - EXPECT_EQ(50, childApzc->GetFrameMetrics().GetScrollOffset().y); - EXPECT_EQ(0, parentApzc->GetFrameMetrics().GetScrollOffset().y); - - // Pan again on the child. This time, since the child was scrolled to - // its end when the gesture began, we expect the scroll to be handed off. - Pan(childApzc, mcc, 60, 50); - - // Verify that the parent scrolled. - EXPECT_EQ(10, parentApzc->GetFrameMetrics().GetScrollOffset().y); -} - -TEST_F(APZOverscrollHandoffTester, ImmediateHandoffDisallowed_Fling) { - SCOPED_GFX_PREF(APZAllowImmediateHandoff, bool, false); - - CreateOverscrollHandoffLayerTree1(); - - RefPtr<TestAsyncPanZoomController> parentApzc = ApzcOf(root); - RefPtr<TestAsyncPanZoomController> childApzc = ApzcOf(layers[1]); - - // Pan on the child, enough to get very close to the end, so that the - // subsequent fling reaches the end and has leftover velocity to hand off. - Pan(childApzc, mcc, 60, 12); - - // Allow the fling to run its course. - childApzc->AdvanceAnimationsUntilEnd(); - parentApzc->AdvanceAnimationsUntilEnd(); - - // Verify that the parent has not scrolled. - // The first comparison needs to be an ASSERT_NEAR because the fling - // computations are such that the final scroll position can be within - // COORDINATE_EPSILON of the end rather than right at the end. - ASSERT_NEAR(50, childApzc->GetFrameMetrics().GetScrollOffset().y, COORDINATE_EPSILON); - EXPECT_EQ(0, parentApzc->GetFrameMetrics().GetScrollOffset().y); - - // Pan again on the child. This time, since the child was scrolled to - // its end when the gesture began, we expect the scroll to be handed off. - Pan(childApzc, mcc, 60, 50); - - // Allow the fling to run its course. The fling should also be handed off. - childApzc->AdvanceAnimationsUntilEnd(); - parentApzc->AdvanceAnimationsUntilEnd(); - - // Verify that the parent scrolled from the fling. - EXPECT_GT(parentApzc->GetFrameMetrics().GetScrollOffset().y, 10); -} - -class APZEventRegionsTester : public APZCTreeManagerTester { -protected: - UniquePtr<ScopedLayerTreeRegistration> registration; - TestAsyncPanZoomController* rootApzc; - - void CreateEventRegionsLayerTree1() { - const char* layerTreeSyntax = "c(tt)"; - nsIntRegion layerVisibleRegions[] = { - nsIntRegion(IntRect(0, 0, 200, 200)), // root - nsIntRegion(IntRect(0, 0, 100, 200)), // left half - nsIntRegion(IntRect(0, 100, 200, 100)), // bottom half - }; - root = CreateLayerTree(layerTreeSyntax, layerVisibleRegions, nullptr, lm, layers); - SetScrollableFrameMetrics(root, FrameMetrics::START_SCROLL_ID); - SetScrollableFrameMetrics(layers[1], FrameMetrics::START_SCROLL_ID + 1); - SetScrollableFrameMetrics(layers[2], FrameMetrics::START_SCROLL_ID + 2); - SetScrollHandoff(layers[1], root); - SetScrollHandoff(layers[2], root); - - // Set up the event regions over a 200x200 area. The root layer has the - // whole 200x200 as the hit region; layers[1] has the left half and - // layers[2] has the bottom half. The bottom-left 100x100 area is also - // in the d-t-c region for both layers[1] and layers[2] (but layers[2] is - // on top so it gets the events by default if the main thread doesn't - // respond). - EventRegions regions(nsIntRegion(IntRect(0, 0, 200, 200))); - root->SetEventRegions(regions); - regions.mDispatchToContentHitRegion = nsIntRegion(IntRect(0, 100, 100, 100)); - regions.mHitRegion = nsIntRegion(IntRect(0, 0, 100, 200)); - layers[1]->SetEventRegions(regions); - regions.mHitRegion = nsIntRegion(IntRect(0, 100, 200, 100)); - layers[2]->SetEventRegions(regions); - - registration = MakeUnique<ScopedLayerTreeRegistration>(manager, 0, root, mcc); - manager->UpdateHitTestingTree(nullptr, root, false, 0, 0); - rootApzc = ApzcOf(root); - } - - void CreateEventRegionsLayerTree2() { - const char* layerTreeSyntax = "c(t)"; - nsIntRegion layerVisibleRegions[] = { - nsIntRegion(IntRect(0, 0, 100, 500)), - nsIntRegion(IntRect(0, 150, 100, 100)), - }; - root = CreateLayerTree(layerTreeSyntax, layerVisibleRegions, nullptr, lm, layers); - SetScrollableFrameMetrics(root, FrameMetrics::START_SCROLL_ID); - - // Set up the event regions so that the child thebes layer is positioned far - // away from the scrolling container layer. - EventRegions regions(nsIntRegion(IntRect(0, 0, 100, 100))); - root->SetEventRegions(regions); - regions.mHitRegion = nsIntRegion(IntRect(0, 150, 100, 100)); - layers[1]->SetEventRegions(regions); - - registration = MakeUnique<ScopedLayerTreeRegistration>(manager, 0, root, mcc); - manager->UpdateHitTestingTree(nullptr, root, false, 0, 0); - rootApzc = ApzcOf(root); - } - - void CreateObscuringLayerTree() { - const char* layerTreeSyntax = "c(c(t)t)"; - // LayerID 0 1 2 3 - // 0 is the root. - // 1 is a parent scrollable layer. - // 2 is a child scrollable layer. - // 3 is the Obscurer, who ruins everything. - nsIntRegion layerVisibleRegions[] = { - // x coordinates are uninteresting - nsIntRegion(IntRect(0, 0, 200, 200)), // [0, 200] - nsIntRegion(IntRect(0, 0, 200, 200)), // [0, 200] - nsIntRegion(IntRect(0, 100, 200, 50)), // [100, 150] - nsIntRegion(IntRect(0, 100, 200, 100)) // [100, 200] - }; - root = CreateLayerTree(layerTreeSyntax, layerVisibleRegions, nullptr, lm, layers); - - SetScrollableFrameMetrics(root, FrameMetrics::START_SCROLL_ID, CSSRect(0, 0, 200, 200)); - SetScrollableFrameMetrics(layers[1], FrameMetrics::START_SCROLL_ID + 1, CSSRect(0, 0, 200, 300)); - SetScrollableFrameMetrics(layers[2], FrameMetrics::START_SCROLL_ID + 2, CSSRect(0, 0, 200, 100)); - SetScrollHandoff(layers[2], layers[1]); - SetScrollHandoff(layers[1], root); - - EventRegions regions(nsIntRegion(IntRect(0, 0, 200, 200))); - root->SetEventRegions(regions); - regions.mHitRegion = nsIntRegion(IntRect(0, 0, 200, 300)); - layers[1]->SetEventRegions(regions); - regions.mHitRegion = nsIntRegion(IntRect(0, 100, 200, 100)); - layers[2]->SetEventRegions(regions); - - registration = MakeUnique<ScopedLayerTreeRegistration>(manager, 0, root, mcc); - manager->UpdateHitTestingTree(nullptr, root, false, 0, 0); - rootApzc = ApzcOf(root); - } - - void CreateBug1119497LayerTree() { - const char* layerTreeSyntax = "c(tt)"; - // LayerID 0 12 - // 0 is the root and has an APZC - // 1 is behind 2 and has an APZC - // 2 entirely covers 1 and should take all the input events, but has no APZC - // so hits to 2 should go to to the root APZC - nsIntRegion layerVisibleRegions[] = { - nsIntRegion(IntRect(0, 0, 100, 100)), - nsIntRegion(IntRect(0, 0, 100, 100)), - nsIntRegion(IntRect(0, 0, 100, 100)), - }; - root = CreateLayerTree(layerTreeSyntax, layerVisibleRegions, nullptr, lm, layers); - - SetScrollableFrameMetrics(root, FrameMetrics::START_SCROLL_ID); - SetScrollableFrameMetrics(layers[1], FrameMetrics::START_SCROLL_ID + 1); - - registration = MakeUnique<ScopedLayerTreeRegistration>(manager, 0, root, mcc); - manager->UpdateHitTestingTree(nullptr, root, false, 0, 0); - } - - void CreateBug1117712LayerTree() { - const char* layerTreeSyntax = "c(c(t)t)"; - // LayerID 0 1 2 3 - // 0 is the root - // 1 is a container layer whose sole purpose to make a non-empty ancestor - // transform for 2, so that 2's screen-to-apzc and apzc-to-gecko - // transforms are different from 3's. - // 2 is a small layer that is the actual target - // 3 is a big layer obscuring 2 with a dispatch-to-content region - nsIntRegion layerVisibleRegions[] = { - nsIntRegion(IntRect(0, 0, 100, 100)), - nsIntRegion(IntRect(0, 0, 0, 0)), - nsIntRegion(IntRect(0, 0, 10, 10)), - nsIntRegion(IntRect(0, 0, 100, 100)), - }; - Matrix4x4 layerTransforms[] = { - Matrix4x4(), - Matrix4x4::Translation(50, 0, 0), - Matrix4x4(), - Matrix4x4(), - }; - root = CreateLayerTree(layerTreeSyntax, layerVisibleRegions, layerTransforms, lm, layers); - - SetScrollableFrameMetrics(layers[2], FrameMetrics::START_SCROLL_ID + 1, CSSRect(0, 0, 10, 10)); - SetScrollableFrameMetrics(layers[3], FrameMetrics::START_SCROLL_ID + 2, CSSRect(0, 0, 100, 100)); - - EventRegions regions(nsIntRegion(IntRect(0, 0, 10, 10))); - layers[2]->SetEventRegions(regions); - regions.mHitRegion = nsIntRegion(IntRect(0, 0, 100, 100)); - regions.mDispatchToContentHitRegion = nsIntRegion(IntRect(0, 0, 100, 100)); - layers[3]->SetEventRegions(regions); - - registration = MakeUnique<ScopedLayerTreeRegistration>(manager, 0, root, mcc); - manager->UpdateHitTestingTree(nullptr, root, false, 0, 0); - } -}; - -TEST_F(APZEventRegionsTester, HitRegionImmediateResponse) { - CreateEventRegionsLayerTree1(); - - TestAsyncPanZoomController* root = ApzcOf(layers[0]); - TestAsyncPanZoomController* left = ApzcOf(layers[1]); - TestAsyncPanZoomController* bottom = ApzcOf(layers[2]); - - MockFunction<void(std::string checkPointName)> check; - { - InSequence s; - EXPECT_CALL(*mcc, HandleSingleTap(_, _, left->GetGuid())).Times(1); - EXPECT_CALL(check, Call("Tapped on left")); - EXPECT_CALL(*mcc, HandleSingleTap(_, _, bottom->GetGuid())).Times(1); - EXPECT_CALL(check, Call("Tapped on bottom")); - EXPECT_CALL(*mcc, HandleSingleTap(_, _, root->GetGuid())).Times(1); - EXPECT_CALL(check, Call("Tapped on root")); - EXPECT_CALL(check, Call("Tap pending on d-t-c region")); - EXPECT_CALL(*mcc, HandleSingleTap(_, _, bottom->GetGuid())).Times(1); - EXPECT_CALL(check, Call("Tapped on bottom again")); - EXPECT_CALL(*mcc, HandleSingleTap(_, _, left->GetGuid())).Times(1); - EXPECT_CALL(check, Call("Tapped on left this time")); - } - - TimeDuration tapDuration = TimeDuration::FromMilliseconds(100); - - // Tap in the exposed hit regions of each of the layers once and ensure - // the clicks are dispatched right away - Tap(manager, 10, 10, mcc, tapDuration); - mcc->RunThroughDelayedTasks(); // this runs the tap event - check.Call("Tapped on left"); - Tap(manager, 110, 110, mcc, tapDuration); - mcc->RunThroughDelayedTasks(); // this runs the tap event - check.Call("Tapped on bottom"); - Tap(manager, 110, 10, mcc, tapDuration); - mcc->RunThroughDelayedTasks(); // this runs the tap event - check.Call("Tapped on root"); - - // Now tap on the dispatch-to-content region where the layers overlap - Tap(manager, 10, 110, mcc, tapDuration); - mcc->RunThroughDelayedTasks(); // this runs the main-thread timeout - check.Call("Tap pending on d-t-c region"); - mcc->RunThroughDelayedTasks(); // this runs the tap event - check.Call("Tapped on bottom again"); - - // Now let's do that again, but simulate a main-thread response - uint64_t inputBlockId = 0; - Tap(manager, 10, 110, mcc, tapDuration, nullptr, &inputBlockId); - nsTArray<ScrollableLayerGuid> targets; - targets.AppendElement(left->GetGuid()); - manager->SetTargetAPZC(inputBlockId, targets); - while (mcc->RunThroughDelayedTasks()); // this runs the tap event - check.Call("Tapped on left this time"); -} - -TEST_F(APZEventRegionsTester, HitRegionAccumulatesChildren) { - CreateEventRegionsLayerTree2(); - - // Tap in the area of the child layer that's not directly included in the - // parent layer's hit region. Verify that it comes out of the APZC's - // content controller, which indicates the input events got routed correctly - // to the APZC. - EXPECT_CALL(*mcc, HandleSingleTap(_, _, rootApzc->GetGuid())).Times(1); - Tap(manager, 10, 160, mcc, TimeDuration::FromMilliseconds(100)); -} - -TEST_F(APZEventRegionsTester, Obscuration) { - CreateObscuringLayerTree(); - ScopedLayerTreeRegistration registration(manager, 0, root, mcc); - - manager->UpdateHitTestingTree(nullptr, root, false, 0, 0); - - TestAsyncPanZoomController* parent = ApzcOf(layers[1]); - TestAsyncPanZoomController* child = ApzcOf(layers[2]); - - ApzcPanNoFling(parent, mcc, 75, 25); - - HitTestResult result; - RefPtr<AsyncPanZoomController> hit = manager->GetTargetAPZC(ScreenPoint(50, 75), &result); - EXPECT_EQ(child, hit.get()); - EXPECT_EQ(HitTestResult::HitLayer, result); -} - -TEST_F(APZEventRegionsTester, Bug1119497) { - CreateBug1119497LayerTree(); - - HitTestResult result; - RefPtr<AsyncPanZoomController> hit = manager->GetTargetAPZC(ScreenPoint(50, 50), &result); - // We should hit layers[2], so |result| will be HitLayer but there's no - // actual APZC on layers[2], so it will be the APZC of the root layer. - EXPECT_EQ(ApzcOf(layers[0]), hit.get()); - EXPECT_EQ(HitTestResult::HitLayer, result); -} - -TEST_F(APZEventRegionsTester, Bug1117712) { - CreateBug1117712LayerTree(); - - TestAsyncPanZoomController* apzc2 = ApzcOf(layers[2]); - - // These touch events should hit the dispatch-to-content region of layers[3] - // and so get queued with that APZC as the tentative target. - uint64_t inputBlockId = 0; - Tap(manager, 55, 5, mcc, TimeDuration::FromMilliseconds(100), nullptr, &inputBlockId); - // But now we tell the APZ that really it hit layers[2], and expect the tap - // to be delivered at the correct coordinates. - EXPECT_CALL(*mcc, HandleSingleTap(CSSPoint(55, 5), 0, apzc2->GetGuid())).Times(1); - - nsTArray<ScrollableLayerGuid> targets; - targets.AppendElement(apzc2->GetGuid()); - manager->SetTargetAPZC(inputBlockId, targets); -} - -class TaskRunMetrics { -public: - TaskRunMetrics() - : mRunCount(0) - , mCancelCount(0) - {} - - void IncrementRunCount() { - mRunCount++; - } - - void IncrementCancelCount() { - mCancelCount++; - } - - int GetAndClearRunCount() { - int runCount = mRunCount; - mRunCount = 0; - return runCount; - } - - int GetAndClearCancelCount() { - int cancelCount = mCancelCount; - mCancelCount = 0; - return cancelCount; - } - -private: - int mRunCount; - int mCancelCount; -}; - -class MockTask : public CancelableTask { -public: - explicit MockTask(TaskRunMetrics& aMetrics) - : mMetrics(aMetrics) - {} - - virtual void Run() { - mMetrics.IncrementRunCount(); - } - - virtual void Cancel() { - mMetrics.IncrementCancelCount(); - } - -private: - TaskRunMetrics& mMetrics; -};
new file mode 100644 --- /dev/null +++ b/gfx/layers/apz/test/gtest/TestBasic.cpp @@ -0,0 +1,348 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set sw=2 ts=8 et 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 "APZCBasicTester.h" +#include "APZTestCommon.h" +#include "InputUtils.h" + +TEST_F(APZCBasicTester, Overzoom) { + // the visible area of the document in CSS pixels is x=10 y=0 w=100 h=100 + FrameMetrics fm; + fm.SetCompositionBounds(ParentLayerRect(0, 0, 100, 100)); + fm.SetScrollableRect(CSSRect(0, 0, 125, 150)); + fm.SetScrollOffset(CSSPoint(10, 0)); + fm.SetZoom(CSSToParentLayerScale2D(1.0, 1.0)); + fm.SetIsRootContent(true); + apzc->SetFrameMetrics(fm); + + MakeApzcZoomable(); + + EXPECT_CALL(*mcc, RequestContentRepaint(_)).Times(1); + + PinchWithPinchInputAndCheckStatus(apzc, 50, 50, 0.5, true); + + fm = apzc->GetFrameMetrics(); + EXPECT_EQ(0.8f, fm.GetZoom().ToScaleFactor().scale); + // bug 936721 - PGO builds introduce rounding error so + // use a fuzzy match instead + EXPECT_LT(std::abs(fm.GetScrollOffset().x), 1e-5); + EXPECT_LT(std::abs(fm.GetScrollOffset().y), 1e-5); +} + +TEST_F(APZCBasicTester, SimpleTransform) { + ParentLayerPoint pointOut; + AsyncTransform viewTransformOut; + apzc->SampleContentTransformForFrame(&viewTransformOut, pointOut); + + EXPECT_EQ(ParentLayerPoint(), pointOut); + EXPECT_EQ(AsyncTransform(), viewTransformOut); +} + + +TEST_F(APZCBasicTester, ComplexTransform) { + // This test assumes there is a page that gets rendered to + // two layers. In CSS pixels, the first layer is 50x50 and + // the second layer is 25x50. The widget scale factor is 3.0 + // and the presShell resolution is 2.0. Therefore, these layers + // end up being 300x300 and 150x300 in layer pixels. + // + // The second (child) layer has an additional CSS transform that + // stretches it by 2.0 on the x-axis. Therefore, after applying + // CSS transforms, the two layers are the same size in screen + // pixels. + // + // The screen itself is 24x24 in screen pixels (therefore 4x4 in + // CSS pixels). The displayport is 1 extra CSS pixel on all + // sides. + + RefPtr<TestAsyncPanZoomController> childApzc = + new TestAsyncPanZoomController(0, mcc, tm); + + const char* layerTreeSyntax = "c(c)"; + // LayerID 0 1 + nsIntRegion layerVisibleRegion[] = { + nsIntRegion(IntRect(0, 0, 300, 300)), + nsIntRegion(IntRect(0, 0, 150, 300)), + }; + Matrix4x4 transforms[] = { + Matrix4x4(), + Matrix4x4(), + }; + transforms[0].PostScale(0.5f, 0.5f, 1.0f); // this results from the 2.0 resolution on the root layer + transforms[1].PostScale(2.0f, 1.0f, 1.0f); // this is the 2.0 x-axis CSS transform on the child layer + + nsTArray<RefPtr<Layer> > layers; + RefPtr<LayerManager> lm; + RefPtr<Layer> root = CreateLayerTree(layerTreeSyntax, layerVisibleRegion, transforms, lm, layers); + + FrameMetrics metrics; + metrics.SetCompositionBounds(ParentLayerRect(0, 0, 24, 24)); + metrics.SetDisplayPort(CSSRect(-1, -1, 6, 6)); + metrics.SetScrollOffset(CSSPoint(10, 10)); + metrics.SetScrollableRect(CSSRect(0, 0, 50, 50)); + metrics.SetCumulativeResolution(LayoutDeviceToLayerScale2D(2, 2)); + metrics.SetPresShellResolution(2.0f); + metrics.SetZoom(CSSToParentLayerScale2D(6, 6)); + metrics.SetDevPixelsPerCSSPixel(CSSToLayoutDeviceScale(3)); + metrics.SetScrollId(FrameMetrics::START_SCROLL_ID); + + FrameMetrics childMetrics = metrics; + childMetrics.SetScrollId(FrameMetrics::START_SCROLL_ID + 1); + + layers[0]->SetFrameMetrics(metrics); + layers[1]->SetFrameMetrics(childMetrics); + + ParentLayerPoint pointOut; + AsyncTransform viewTransformOut; + + // Both the parent and child layer should behave exactly the same here, because + // the CSS transform on the child layer does not affect the SampleContentTransformForFrame code + + // initial transform + apzc->SetFrameMetrics(metrics); + apzc->NotifyLayersUpdated(metrics, true, true); + apzc->SampleContentTransformForFrame(&viewTransformOut, pointOut); + EXPECT_EQ(AsyncTransform(LayerToParentLayerScale(1), ParentLayerPoint()), viewTransformOut); + EXPECT_EQ(ParentLayerPoint(60, 60), pointOut); + + childApzc->SetFrameMetrics(childMetrics); + childApzc->NotifyLayersUpdated(childMetrics, true, true); + childApzc->SampleContentTransformForFrame(&viewTransformOut, pointOut); + EXPECT_EQ(AsyncTransform(LayerToParentLayerScale(1), ParentLayerPoint()), viewTransformOut); + EXPECT_EQ(ParentLayerPoint(60, 60), pointOut); + + // do an async scroll by 5 pixels and check the transform + metrics.ScrollBy(CSSPoint(5, 0)); + apzc->SetFrameMetrics(metrics); + apzc->SampleContentTransformForFrame(&viewTransformOut, pointOut); + EXPECT_EQ(AsyncTransform(LayerToParentLayerScale(1), ParentLayerPoint(-30, 0)), viewTransformOut); + EXPECT_EQ(ParentLayerPoint(90, 60), pointOut); + + childMetrics.ScrollBy(CSSPoint(5, 0)); + childApzc->SetFrameMetrics(childMetrics); + childApzc->SampleContentTransformForFrame(&viewTransformOut, pointOut); + EXPECT_EQ(AsyncTransform(LayerToParentLayerScale(1), ParentLayerPoint(-30, 0)), viewTransformOut); + EXPECT_EQ(ParentLayerPoint(90, 60), pointOut); + + // do an async zoom of 1.5x and check the transform + metrics.ZoomBy(1.5f); + apzc->SetFrameMetrics(metrics); + apzc->SampleContentTransformForFrame(&viewTransformOut, pointOut); + EXPECT_EQ(AsyncTransform(LayerToParentLayerScale(1.5), ParentLayerPoint(-45, 0)), viewTransformOut); + EXPECT_EQ(ParentLayerPoint(135, 90), pointOut); + + childMetrics.ZoomBy(1.5f); + childApzc->SetFrameMetrics(childMetrics); + childApzc->SampleContentTransformForFrame(&viewTransformOut, pointOut); + EXPECT_EQ(AsyncTransform(LayerToParentLayerScale(1.5), ParentLayerPoint(-45, 0)), viewTransformOut); + EXPECT_EQ(ParentLayerPoint(135, 90), pointOut); + + childApzc->Destroy(); +} + +TEST_F(APZCBasicTester, Fling) { + int touchStart = 50; + int touchEnd = 10; + ParentLayerPoint pointOut; + AsyncTransform viewTransformOut; + + // Fling down. Each step scroll further down + Pan(apzc, mcc, touchStart, touchEnd); + ParentLayerPoint lastPoint; + for (int i = 1; i < 50; i+=1) { + apzc->SampleContentTransformForFrame(&viewTransformOut, pointOut, TimeDuration::FromMilliseconds(1)); + EXPECT_GT(pointOut.y, lastPoint.y); + lastPoint = pointOut; + } +} + +TEST_F(APZCBasicTester, FlingIntoOverscroll) { + // Enable overscrolling. + SCOPED_GFX_PREF(APZOverscrollEnabled, bool, true); + + // Scroll down by 25 px. Don't fling for simplicity. + ApzcPanNoFling(apzc, mcc, 50, 25); + + // Now scroll back up by 20px, this time flinging after. + // The fling should cover the remaining 5 px of room to scroll, then + // go into overscroll, and finally snap-back to recover from overscroll. + Pan(apzc, mcc, 25, 45); + const TimeDuration increment = TimeDuration::FromMilliseconds(1); + bool reachedOverscroll = false; + bool recoveredFromOverscroll = false; + while (apzc->AdvanceAnimations(mcc->Time())) { + if (!reachedOverscroll && apzc->IsOverscrolled()) { + reachedOverscroll = true; + } + if (reachedOverscroll && !apzc->IsOverscrolled()) { + recoveredFromOverscroll = true; + } + mcc->AdvanceBy(increment); + } + EXPECT_TRUE(reachedOverscroll); + EXPECT_TRUE(recoveredFromOverscroll); +} + +TEST_F(APZCBasicTester, PanningTransformNotifications) { + SCOPED_GFX_PREF(APZOverscrollEnabled, bool, true); + + // Scroll down by 25 px. Ensure we only get one set of + // state change notifications. + // + // Then, scroll back up by 20px, this time flinging after. + // The fling should cover the remaining 5 px of room to scroll, then + // go into overscroll, and finally snap-back to recover from overscroll. + // Again, ensure we only get one set of state change notifications for + // this entire procedure. + + MockFunction<void(std::string checkPointName)> check; + { + InSequence s; + EXPECT_CALL(check, Call("Simple pan")); + EXPECT_CALL(*mcc, NotifyAPZStateChange(_,GeckoContentController::APZStateChange::StartTouch,_)).Times(1); + EXPECT_CALL(*mcc, NotifyAPZStateChange(_,GeckoContentController::APZStateChange::TransformBegin,_)).Times(1); + EXPECT_CALL(*mcc, NotifyAPZStateChange(_,GeckoContentController::APZStateChange::StartPanning,_)).Times(1); + EXPECT_CALL(*mcc, NotifyAPZStateChange(_,GeckoContentController::APZStateChange::EndTouch,_)).Times(1); + EXPECT_CALL(*mcc, NotifyAPZStateChange(_,GeckoContentController::APZStateChange::TransformEnd,_)).Times(1); + EXPECT_CALL(check, Call("Complex pan")); + EXPECT_CALL(*mcc, NotifyAPZStateChange(_,GeckoContentController::APZStateChange::StartTouch,_)).Times(1); + EXPECT_CALL(*mcc, NotifyAPZStateChange(_,GeckoContentController::APZStateChange::TransformBegin,_)).Times(1); + EXPECT_CALL(*mcc, NotifyAPZStateChange(_,GeckoContentController::APZStateChange::StartPanning,_)).Times(1); + EXPECT_CALL(*mcc, NotifyAPZStateChange(_,GeckoContentController::APZStateChange::EndTouch,_)).Times(1); + EXPECT_CALL(*mcc, NotifyAPZStateChange(_,GeckoContentController::APZStateChange::TransformEnd,_)).Times(1); + EXPECT_CALL(check, Call("Done")); + } + + check.Call("Simple pan"); + ApzcPanNoFling(apzc, mcc, 50, 25); + check.Call("Complex pan"); + Pan(apzc, mcc, 25, 45); + apzc->AdvanceAnimationsUntilEnd(); + check.Call("Done"); +} + +void APZCBasicTester::PanIntoOverscroll() +{ + int touchStart = 500; + int touchEnd = 10; + Pan(apzc, mcc, touchStart, touchEnd); + EXPECT_TRUE(apzc->IsOverscrolled()); +} + +void APZCBasicTester::TestOverscroll() +{ + // Pan sufficiently to hit overscroll behavior + PanIntoOverscroll(); + + // Check that we recover from overscroll via an animation. + ParentLayerPoint expectedScrollOffset(0, GetScrollRange().YMost()); + SampleAnimationUntilRecoveredFromOverscroll(expectedScrollOffset); +} + + +TEST_F(APZCBasicTester, OverScrollPanning) { + SCOPED_GFX_PREF(APZOverscrollEnabled, bool, true); + + TestOverscroll(); +} + +// Tests that an overscroll animation doesn't trigger an assertion failure +// in the case where a sample has a velocity of zero. +TEST_F(APZCBasicTester, OverScroll_Bug1152051a) { + SCOPED_GFX_PREF(APZOverscrollEnabled, bool, true); + + // Doctor the prefs to make the velocity zero at the end of the first sample. + + // This ensures our incoming velocity to the overscroll animation is + // a round(ish) number, 4.9 (that being the distance of the pan before + // overscroll, which is 500 - 10 = 490 pixels, divided by the duration of + // the pan, which is 100 ms). + SCOPED_GFX_PREF(APZFlingFriction, float, 0); + + // To ensure the velocity after the first sample is 0, set the spring + // stiffness to the incoming velocity (4.9) divided by the overscroll + // (400 pixels) times the step duration (1 ms). + SCOPED_GFX_PREF(APZOverscrollSpringStiffness, float, 0.01225f); + + TestOverscroll(); +} + +// Tests that ending an overscroll animation doesn't leave around state that +// confuses the next overscroll animation. +TEST_F(APZCBasicTester, OverScroll_Bug1152051b) { + SCOPED_GFX_PREF(APZOverscrollEnabled, bool, true); + + SCOPED_GFX_PREF(APZOverscrollStopDistanceThreshold, float, 0.1f); + + // Pan sufficiently to hit overscroll behavior + PanIntoOverscroll(); + + // Sample animations once, to give the fling animation started on touch-up + // a chance to realize it's overscrolled, and schedule a call to + // HandleFlingOverscroll(). + SampleAnimationOnce(); + + // This advances the time and runs the HandleFlingOverscroll task scheduled in + // the previous call, which starts an overscroll animation. It then samples + // the overscroll animation once, to get it to initialize the first overscroll + // sample. + SampleAnimationOnce(); + + // Do a touch-down to cancel the overscroll animation, and then a touch-up + // to schedule a new one since we're still overscrolled. We don't pan because + // panning can trigger functions that clear the overscroll animation state + // in other ways. + TouchDown(apzc, 10, 10, mcc->Time(), nullptr); + TouchUp(apzc, 10, 10, mcc->Time()); + + // Sample the second overscroll animation to its end. + // If the ending of the first overscroll animation fails to clear state + // properly, this will assert. + ParentLayerPoint expectedScrollOffset(0, GetScrollRange().YMost()); + SampleAnimationUntilRecoveredFromOverscroll(expectedScrollOffset); +} + +TEST_F(APZCBasicTester, OverScrollAbort) { + SCOPED_GFX_PREF(APZOverscrollEnabled, bool, true); + + // Pan sufficiently to hit overscroll behavior + int touchStart = 500; + int touchEnd = 10; + Pan(apzc, mcc, touchStart, touchEnd); + EXPECT_TRUE(apzc->IsOverscrolled()); + + ParentLayerPoint pointOut; + AsyncTransform viewTransformOut; + + // This sample call will run to the end of the fling animation + // and will schedule the overscroll animation. + apzc->SampleContentTransformForFrame(&viewTransformOut, pointOut, TimeDuration::FromMilliseconds(10000)); + EXPECT_TRUE(apzc->IsOverscrolled()); + + // At this point, we have an active overscroll animation. + // Check that cancelling the animation clears the overscroll. + apzc->CancelAnimation(); + EXPECT_FALSE(apzc->IsOverscrolled()); + apzc->AssertStateIsReset(); +} + +TEST_F(APZCBasicTester, OverScrollPanningAbort) { + SCOPED_GFX_PREF(APZOverscrollEnabled, bool, true); + + // Pan sufficiently to hit overscroll behaviour. Keep the finger down so + // the pan does not end. + int touchStart = 500; + int touchEnd = 10; + Pan(apzc, mcc, touchStart, touchEnd, true); // keep finger down + EXPECT_TRUE(apzc->IsOverscrolled()); + + // Check that calling CancelAnimation() while the user is still panning + // (and thus no fling or snap-back animation has had a chance to start) + // clears the overscroll. + apzc->CancelAnimation(); + EXPECT_FALSE(apzc->IsOverscrolled()); + apzc->AssertStateIsReset(); +}
new file mode 100644 --- /dev/null +++ b/gfx/layers/apz/test/gtest/TestEventRegions.cpp @@ -0,0 +1,271 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set sw=2 ts=8 et 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 "APZCTreeManagerTester.h" +#include "APZTestCommon.h" +#include "InputUtils.h" + +class APZEventRegionsTester : public APZCTreeManagerTester { +protected: + UniquePtr<ScopedLayerTreeRegistration> registration; + TestAsyncPanZoomController* rootApzc; + + void CreateEventRegionsLayerTree1() { + const char* layerTreeSyntax = "c(tt)"; + nsIntRegion layerVisibleRegions[] = { + nsIntRegion(IntRect(0, 0, 200, 200)), // root + nsIntRegion(IntRect(0, 0, 100, 200)), // left half + nsIntRegion(IntRect(0, 100, 200, 100)), // bottom half + }; + root = CreateLayerTree(layerTreeSyntax, layerVisibleRegions, nullptr, lm, layers); + SetScrollableFrameMetrics(root, FrameMetrics::START_SCROLL_ID); + SetScrollableFrameMetrics(layers[1], FrameMetrics::START_SCROLL_ID + 1); + SetScrollableFrameMetrics(layers[2], FrameMetrics::START_SCROLL_ID + 2); + SetScrollHandoff(layers[1], root); + SetScrollHandoff(layers[2], root); + + // Set up the event regions over a 200x200 area. The root layer has the + // whole 200x200 as the hit region; layers[1] has the left half and + // layers[2] has the bottom half. The bottom-left 100x100 area is also + // in the d-t-c region for both layers[1] and layers[2] (but layers[2] is + // on top so it gets the events by default if the main thread doesn't + // respond). + EventRegions regions(nsIntRegion(IntRect(0, 0, 200, 200))); + root->SetEventRegions(regions); + regions.mDispatchToContentHitRegion = nsIntRegion(IntRect(0, 100, 100, 100)); + regions.mHitRegion = nsIntRegion(IntRect(0, 0, 100, 200)); + layers[1]->SetEventRegions(regions); + regions.mHitRegion = nsIntRegion(IntRect(0, 100, 200, 100)); + layers[2]->SetEventRegions(regions); + + registration = MakeUnique<ScopedLayerTreeRegistration>(manager, 0, root, mcc); + manager->UpdateHitTestingTree(nullptr, root, false, 0, 0); + rootApzc = ApzcOf(root); + } + + void CreateEventRegionsLayerTree2() { + const char* layerTreeSyntax = "c(t)"; + nsIntRegion layerVisibleRegions[] = { + nsIntRegion(IntRect(0, 0, 100, 500)), + nsIntRegion(IntRect(0, 150, 100, 100)), + }; + root = CreateLayerTree(layerTreeSyntax, layerVisibleRegions, nullptr, lm, layers); + SetScrollableFrameMetrics(root, FrameMetrics::START_SCROLL_ID); + + // Set up the event regions so that the child thebes layer is positioned far + // away from the scrolling container layer. + EventRegions regions(nsIntRegion(IntRect(0, 0, 100, 100))); + root->SetEventRegions(regions); + regions.mHitRegion = nsIntRegion(IntRect(0, 150, 100, 100)); + layers[1]->SetEventRegions(regions); + + registration = MakeUnique<ScopedLayerTreeRegistration>(manager, 0, root, mcc); + manager->UpdateHitTestingTree(nullptr, root, false, 0, 0); + rootApzc = ApzcOf(root); + } + + void CreateObscuringLayerTree() { + const char* layerTreeSyntax = "c(c(t)t)"; + // LayerID 0 1 2 3 + // 0 is the root. + // 1 is a parent scrollable layer. + // 2 is a child scrollable layer. + // 3 is the Obscurer, who ruins everything. + nsIntRegion layerVisibleRegions[] = { + // x coordinates are uninteresting + nsIntRegion(IntRect(0, 0, 200, 200)), // [0, 200] + nsIntRegion(IntRect(0, 0, 200, 200)), // [0, 200] + nsIntRegion(IntRect(0, 100, 200, 50)), // [100, 150] + nsIntRegion(IntRect(0, 100, 200, 100)) // [100, 200] + }; + root = CreateLayerTree(layerTreeSyntax, layerVisibleRegions, nullptr, lm, layers); + + SetScrollableFrameMetrics(root, FrameMetrics::START_SCROLL_ID, CSSRect(0, 0, 200, 200)); + SetScrollableFrameMetrics(layers[1], FrameMetrics::START_SCROLL_ID + 1, CSSRect(0, 0, 200, 300)); + SetScrollableFrameMetrics(layers[2], FrameMetrics::START_SCROLL_ID + 2, CSSRect(0, 0, 200, 100)); + SetScrollHandoff(layers[2], layers[1]); + SetScrollHandoff(layers[1], root); + + EventRegions regions(nsIntRegion(IntRect(0, 0, 200, 200))); + root->SetEventRegions(regions); + regions.mHitRegion = nsIntRegion(IntRect(0, 0, 200, 300)); + layers[1]->SetEventRegions(regions); + regions.mHitRegion = nsIntRegion(IntRect(0, 100, 200, 100)); + layers[2]->SetEventRegions(regions); + + registration = MakeUnique<ScopedLayerTreeRegistration>(manager, 0, root, mcc); + manager->UpdateHitTestingTree(nullptr, root, false, 0, 0); + rootApzc = ApzcOf(root); + } + + void CreateBug1119497LayerTree() { + const char* layerTreeSyntax = "c(tt)"; + // LayerID 0 12 + // 0 is the root and has an APZC + // 1 is behind 2 and has an APZC + // 2 entirely covers 1 and should take all the input events, but has no APZC + // so hits to 2 should go to to the root APZC + nsIntRegion layerVisibleRegions[] = { + nsIntRegion(IntRect(0, 0, 100, 100)), + nsIntRegion(IntRect(0, 0, 100, 100)), + nsIntRegion(IntRect(0, 0, 100, 100)), + }; + root = CreateLayerTree(layerTreeSyntax, layerVisibleRegions, nullptr, lm, layers); + + SetScrollableFrameMetrics(root, FrameMetrics::START_SCROLL_ID); + SetScrollableFrameMetrics(layers[1], FrameMetrics::START_SCROLL_ID + 1); + + registration = MakeUnique<ScopedLayerTreeRegistration>(manager, 0, root, mcc); + manager->UpdateHitTestingTree(nullptr, root, false, 0, 0); + } + + void CreateBug1117712LayerTree() { + const char* layerTreeSyntax = "c(c(t)t)"; + // LayerID 0 1 2 3 + // 0 is the root + // 1 is a container layer whose sole purpose to make a non-empty ancestor + // transform for 2, so that 2's screen-to-apzc and apzc-to-gecko + // transforms are different from 3's. + // 2 is a small layer that is the actual target + // 3 is a big layer obscuring 2 with a dispatch-to-content region + nsIntRegion layerVisibleRegions[] = { + nsIntRegion(IntRect(0, 0, 100, 100)), + nsIntRegion(IntRect(0, 0, 0, 0)), + nsIntRegion(IntRect(0, 0, 10, 10)), + nsIntRegion(IntRect(0, 0, 100, 100)), + }; + Matrix4x4 layerTransforms[] = { + Matrix4x4(), + Matrix4x4::Translation(50, 0, 0), + Matrix4x4(), + Matrix4x4(), + }; + root = CreateLayerTree(layerTreeSyntax, layerVisibleRegions, layerTransforms, lm, layers); + + SetScrollableFrameMetrics(layers[2], FrameMetrics::START_SCROLL_ID + 1, CSSRect(0, 0, 10, 10)); + SetScrollableFrameMetrics(layers[3], FrameMetrics::START_SCROLL_ID + 2, CSSRect(0, 0, 100, 100)); + + EventRegions regions(nsIntRegion(IntRect(0, 0, 10, 10))); + layers[2]->SetEventRegions(regions); + regions.mHitRegion = nsIntRegion(IntRect(0, 0, 100, 100)); + regions.mDispatchToContentHitRegion = nsIntRegion(IntRect(0, 0, 100, 100)); + layers[3]->SetEventRegions(regions); + + registration = MakeUnique<ScopedLayerTreeRegistration>(manager, 0, root, mcc); + manager->UpdateHitTestingTree(nullptr, root, false, 0, 0); + } +}; + +TEST_F(APZEventRegionsTester, HitRegionImmediateResponse) { + CreateEventRegionsLayerTree1(); + + TestAsyncPanZoomController* root = ApzcOf(layers[0]); + TestAsyncPanZoomController* left = ApzcOf(layers[1]); + TestAsyncPanZoomController* bottom = ApzcOf(layers[2]); + + MockFunction<void(std::string checkPointName)> check; + { + InSequence s; + EXPECT_CALL(*mcc, HandleSingleTap(_, _, left->GetGuid())).Times(1); + EXPECT_CALL(check, Call("Tapped on left")); + EXPECT_CALL(*mcc, HandleSingleTap(_, _, bottom->GetGuid())).Times(1); + EXPECT_CALL(check, Call("Tapped on bottom")); + EXPECT_CALL(*mcc, HandleSingleTap(_, _, root->GetGuid())).Times(1); + EXPECT_CALL(check, Call("Tapped on root")); + EXPECT_CALL(check, Call("Tap pending on d-t-c region")); + EXPECT_CALL(*mcc, HandleSingleTap(_, _, bottom->GetGuid())).Times(1); + EXPECT_CALL(check, Call("Tapped on bottom again")); + EXPECT_CALL(*mcc, HandleSingleTap(_, _, left->GetGuid())).Times(1); + EXPECT_CALL(check, Call("Tapped on left this time")); + } + + TimeDuration tapDuration = TimeDuration::FromMilliseconds(100); + + // Tap in the exposed hit regions of each of the layers once and ensure + // the clicks are dispatched right away + Tap(manager, 10, 10, mcc, tapDuration); + mcc->RunThroughDelayedTasks(); // this runs the tap event + check.Call("Tapped on left"); + Tap(manager, 110, 110, mcc, tapDuration); + mcc->RunThroughDelayedTasks(); // this runs the tap event + check.Call("Tapped on bottom"); + Tap(manager, 110, 10, mcc, tapDuration); + mcc->RunThroughDelayedTasks(); // this runs the tap event + check.Call("Tapped on root"); + + // Now tap on the dispatch-to-content region where the layers overlap + Tap(manager, 10, 110, mcc, tapDuration); + mcc->RunThroughDelayedTasks(); // this runs the main-thread timeout + check.Call("Tap pending on d-t-c region"); + mcc->RunThroughDelayedTasks(); // this runs the tap event + check.Call("Tapped on bottom again"); + + // Now let's do that again, but simulate a main-thread response + uint64_t inputBlockId = 0; + Tap(manager, 10, 110, mcc, tapDuration, nullptr, &inputBlockId); + nsTArray<ScrollableLayerGuid> targets; + targets.AppendElement(left->GetGuid()); + manager->SetTargetAPZC(inputBlockId, targets); + while (mcc->RunThroughDelayedTasks()); // this runs the tap event + check.Call("Tapped on left this time"); +} + +TEST_F(APZEventRegionsTester, HitRegionAccumulatesChildren) { + CreateEventRegionsLayerTree2(); + + // Tap in the area of the child layer that's not directly included in the + // parent layer's hit region. Verify that it comes out of the APZC's + // content controller, which indicates the input events got routed correctly + // to the APZC. + EXPECT_CALL(*mcc, HandleSingleTap(_, _, rootApzc->GetGuid())).Times(1); + Tap(manager, 10, 160, mcc, TimeDuration::FromMilliseconds(100)); +} + +TEST_F(APZEventRegionsTester, Obscuration) { + CreateObscuringLayerTree(); + ScopedLayerTreeRegistration registration(manager, 0, root, mcc); + + manager->UpdateHitTestingTree(nullptr, root, false, 0, 0); + + TestAsyncPanZoomController* parent = ApzcOf(layers[1]); + TestAsyncPanZoomController* child = ApzcOf(layers[2]); + + ApzcPanNoFling(parent, mcc, 75, 25); + + HitTestResult result; + RefPtr<AsyncPanZoomController> hit = manager->GetTargetAPZC(ScreenPoint(50, 75), &result); + EXPECT_EQ(child, hit.get()); + EXPECT_EQ(HitTestResult::HitLayer, result); +} + +TEST_F(APZEventRegionsTester, Bug1119497) { + CreateBug1119497LayerTree(); + + HitTestResult result; + RefPtr<AsyncPanZoomController> hit = manager->GetTargetAPZC(ScreenPoint(50, 50), &result); + // We should hit layers[2], so |result| will be HitLayer but there's no + // actual APZC on layers[2], so it will be the APZC of the root layer. + EXPECT_EQ(ApzcOf(layers[0]), hit.get()); + EXPECT_EQ(HitTestResult::HitLayer, result); +} + +TEST_F(APZEventRegionsTester, Bug1117712) { + CreateBug1117712LayerTree(); + + TestAsyncPanZoomController* apzc2 = ApzcOf(layers[2]); + + // These touch events should hit the dispatch-to-content region of layers[3] + // and so get queued with that APZC as the tentative target. + uint64_t inputBlockId = 0; + Tap(manager, 55, 5, mcc, TimeDuration::FromMilliseconds(100), nullptr, &inputBlockId); + // But now we tell the APZ that really it hit layers[2], and expect the tap + // to be delivered at the correct coordinates. + EXPECT_CALL(*mcc, HandleSingleTap(CSSPoint(55, 5), 0, apzc2->GetGuid())).Times(1); + + nsTArray<ScrollableLayerGuid> targets; + targets.AppendElement(apzc2->GetGuid()); + manager->SetTargetAPZC(inputBlockId, targets); +}
new file mode 100644 --- /dev/null +++ b/gfx/layers/apz/test/gtest/TestGestureDetector.cpp @@ -0,0 +1,594 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set sw=2 ts=8 et 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 "APZCBasicTester.h" +#include "APZTestCommon.h" + +class APZCGestureDetectorTester : public APZCBasicTester { +public: + APZCGestureDetectorTester() + : APZCBasicTester(AsyncPanZoomController::USE_GESTURE_DETECTOR) + { + } + +protected: + FrameMetrics GetPinchableFrameMetrics() + { + FrameMetrics fm; + fm.SetCompositionBounds(ParentLayerRect(200, 200, 100, 200)); + fm.SetScrollableRect(CSSRect(0, 0, 980, 1000)); + fm.SetScrollOffset(CSSPoint(300, 300)); + fm.SetZoom(CSSToParentLayerScale2D(2.0, 2.0)); + // APZC only allows zooming on the root scrollable frame. + fm.SetIsRootContent(true); + // the visible area of the document in CSS pixels is x=300 y=300 w=50 h=100 + return fm; + } +}; + +TEST_F(APZCGestureDetectorTester, Pan_After_Pinch) { + SCOPED_GFX_PREF(TouchActionEnabled, bool, false); + + FrameMetrics originalMetrics = GetPinchableFrameMetrics(); + apzc->SetFrameMetrics(originalMetrics); + + MakeApzcZoomable(); + + // Test parameters + float zoomAmount = 1.25; + float pinchLength = 100.0; + float pinchLengthScaled = pinchLength * zoomAmount; + int focusX = 250; + int focusY = 300; + int panDistance = 20; + + int firstFingerId = 0; + int secondFingerId = firstFingerId + 1; + + // Put fingers down + MultiTouchInput mti = MultiTouchInput(MultiTouchInput::MULTITOUCH_START, 0, TimeStamp(), 0); + mti.mTouches.AppendElement(CreateSingleTouchData(firstFingerId, focusX, focusY)); + mti.mTouches.AppendElement(CreateSingleTouchData(secondFingerId, focusX, focusY)); + apzc->ReceiveInputEvent(mti, nullptr); + + // Spread fingers out to enter the pinch state + mti = MultiTouchInput(MultiTouchInput::MULTITOUCH_MOVE, 0, TimeStamp(), 0); + mti.mTouches.AppendElement(CreateSingleTouchData(firstFingerId, focusX - pinchLength, focusY)); + mti.mTouches.AppendElement(CreateSingleTouchData(secondFingerId, focusX + pinchLength, focusY)); + apzc->ReceiveInputEvent(mti, nullptr); + + // Do the actual pinch of 1.25x + mti = MultiTouchInput(MultiTouchInput::MULTITOUCH_MOVE, 0, TimeStamp(), 0); + mti.mTouches.AppendElement(CreateSingleTouchData(firstFingerId, focusX - pinchLengthScaled, focusY)); + mti.mTouches.AppendElement(CreateSingleTouchData(secondFingerId, focusX + pinchLengthScaled, focusY)); + apzc->ReceiveInputEvent(mti, nullptr); + + // Verify that the zoom changed, just to make sure our code above did what it + // was supposed to. + FrameMetrics zoomedMetrics = apzc->GetFrameMetrics(); + float newZoom = zoomedMetrics.GetZoom().ToScaleFactor().scale; + EXPECT_EQ(originalMetrics.GetZoom().ToScaleFactor().scale * zoomAmount, newZoom); + + // Now we lift one finger... + mti = MultiTouchInput(MultiTouchInput::MULTITOUCH_END, 0, TimeStamp(), 0); + mti.mTouches.AppendElement(CreateSingleTouchData(secondFingerId, focusX + pinchLengthScaled, focusY)); + apzc->ReceiveInputEvent(mti, nullptr); + + // ... and pan with the remaining finger. This pan just breaks through the + // distance threshold. + focusY += 40; + mti = MultiTouchInput(MultiTouchInput::MULTITOUCH_MOVE, 0, TimeStamp(), 0); + mti.mTouches.AppendElement(CreateSingleTouchData(firstFingerId, focusX - pinchLengthScaled, focusY)); + apzc->ReceiveInputEvent(mti, nullptr); + + // This one does an actual pan of 20 pixels + focusY += panDistance; + mti = MultiTouchInput(MultiTouchInput::MULTITOUCH_MOVE, 0, TimeStamp(), 0); + mti.mTouches.AppendElement(CreateSingleTouchData(firstFingerId, focusX - pinchLengthScaled, focusY)); + apzc->ReceiveInputEvent(mti, nullptr); + + // Lift the remaining finger + mti = MultiTouchInput(MultiTouchInput::MULTITOUCH_END, 0, TimeStamp(), 0); + mti.mTouches.AppendElement(CreateSingleTouchData(firstFingerId, focusX - pinchLengthScaled, focusY)); + apzc->ReceiveInputEvent(mti, nullptr); + + // Verify that we scrolled + FrameMetrics finalMetrics = apzc->GetFrameMetrics(); + EXPECT_EQ(zoomedMetrics.GetScrollOffset().y - (panDistance / newZoom), finalMetrics.GetScrollOffset().y); + + // Clear out any remaining fling animation and pending tasks + apzc->AdvanceAnimationsUntilEnd(); + while (mcc->RunThroughDelayedTasks()); + 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(); +} + +class APZCFlingStopTester : public APZCGestureDetectorTester { +protected: + // Start a fling, and then tap while the fling is ongoing. When + // aSlow is false, the tap will happen while the fling is at a + // high velocity, and we check that the tap doesn't trigger sending a tap + // to content. If aSlow is true, the tap will happen while the fling + // is at a slow velocity, and we check that the tap does trigger sending + // a tap to content. See bug 1022956. + void DoFlingStopTest(bool aSlow) { + int touchStart = 50; + int touchEnd = 10; + + // Start the fling down. + Pan(apzc, mcc, touchStart, touchEnd); + // The touchstart from the pan will leave some cancelled tasks in the queue, clear them out + + // If we want to tap while the fling is fast, let the fling advance for 10ms only. If we want + // the fling to slow down more, advance to 2000ms. These numbers may need adjusting if our + // friction and threshold values change, but they should be deterministic at least. + int timeDelta = aSlow ? 2000 : 10; + int tapCallsExpected = aSlow ? 2 : 1; + + // Advance the fling animation by timeDelta milliseconds. + ParentLayerPoint pointOut; + AsyncTransform viewTransformOut; + apzc->SampleContentTransformForFrame(&viewTransformOut, pointOut, TimeDuration::FromMilliseconds(timeDelta)); + + // Deliver a tap to abort the fling. Ensure that we get a HandleSingleTap + // call out of it if and only if the fling is slow. + EXPECT_CALL(*mcc, HandleSingleTap(_, 0, apzc->GetGuid())).Times(tapCallsExpected); + Tap(apzc, 10, 10, mcc, 0); + while (mcc->RunThroughDelayedTasks()); + + // Deliver another tap, to make sure that taps are flowing properly once + // the fling is aborted. + Tap(apzc, 100, 100, mcc, 0); + while (mcc->RunThroughDelayedTasks()); + + // Verify that we didn't advance any further after the fling was aborted, in either case. + ParentLayerPoint finalPointOut; + apzc->SampleContentTransformForFrame(&viewTransformOut, finalPointOut); + EXPECT_EQ(pointOut.x, finalPointOut.x); + EXPECT_EQ(pointOut.y, finalPointOut.y); + + apzc->AssertStateIsReset(); + } + + void DoFlingStopWithSlowListener(bool aPreventDefault) { + MakeApzcWaitForMainThread(); + + int touchStart = 50; + int touchEnd = 10; + uint64_t blockId = 0; + + // Start the fling down. + Pan(apzc, mcc, touchStart, touchEnd, false, nullptr, nullptr, &blockId); + apzc->ConfirmTarget(blockId); + apzc->ContentReceivedInputBlock(blockId, false); + + // Sample the fling a couple of times to ensure it's going. + ParentLayerPoint point, finalPoint; + AsyncTransform viewTransform; + apzc->SampleContentTransformForFrame(&viewTransform, point, TimeDuration::FromMilliseconds(10)); + apzc->SampleContentTransformForFrame(&viewTransform, finalPoint, TimeDuration::FromMilliseconds(10)); + EXPECT_GT(finalPoint.y, point.y); + + // Now we put our finger down to stop the fling + TouchDown(apzc, 10, 10, mcc->Time(), &blockId); + + // Re-sample to make sure it hasn't moved + apzc->SampleContentTransformForFrame(&viewTransform, point, TimeDuration::FromMilliseconds(10)); + EXPECT_EQ(finalPoint.x, point.x); + EXPECT_EQ(finalPoint.y, point.y); + + // respond to the touchdown that stopped the fling. + // even if we do a prevent-default on it, the animation should remain stopped. + apzc->ContentReceivedInputBlock(blockId, aPreventDefault); + + // Verify the page hasn't moved + apzc->SampleContentTransformForFrame(&viewTransform, point, TimeDuration::FromMilliseconds(70)); + EXPECT_EQ(finalPoint.x, point.x); + EXPECT_EQ(finalPoint.y, point.y); + + // clean up + TouchUp(apzc, 10, 10, mcc->Time()); + + apzc->AssertStateIsReset(); + } +}; + +TEST_F(APZCFlingStopTester, FlingStop) { + DoFlingStopTest(false); +} + +TEST_F(APZCFlingStopTester, FlingStopTap) { + DoFlingStopTest(true); +} + +TEST_F(APZCFlingStopTester, FlingStopSlowListener) { + DoFlingStopWithSlowListener(false); +} + +TEST_F(APZCFlingStopTester, FlingStopPreventDefault) { + DoFlingStopWithSlowListener(true); +} + +TEST_F(APZCGestureDetectorTester, ShortPress) { + MakeApzcUnzoomable(); + + MockFunction<void(std::string checkPointName)> check; + { + InSequence s; + // This verifies that the single tap notification is sent after the + // touchup is fully processed. The ordering here is important. + EXPECT_CALL(check, Call("pre-tap")); + EXPECT_CALL(check, Call("post-tap")); + EXPECT_CALL(*mcc, HandleSingleTap(CSSPoint(10, 10), 0, apzc->GetGuid())).Times(1); + } + + check.Call("pre-tap"); + TapAndCheckStatus(apzc, 10, 10, mcc, TimeDuration::FromMilliseconds(100)); + check.Call("post-tap"); + + apzc->AssertStateIsReset(); +} + +TEST_F(APZCGestureDetectorTester, MediumPress) { + MakeApzcUnzoomable(); + + MockFunction<void(std::string checkPointName)> check; + { + InSequence s; + // This verifies that the single tap notification is sent after the + // touchup is fully processed. The ordering here is important. + EXPECT_CALL(check, Call("pre-tap")); + EXPECT_CALL(check, Call("post-tap")); + EXPECT_CALL(*mcc, HandleSingleTap(CSSPoint(10, 10), 0, apzc->GetGuid())).Times(1); + } + + check.Call("pre-tap"); + TapAndCheckStatus(apzc, 10, 10, mcc, TimeDuration::FromMilliseconds(400)); + check.Call("post-tap"); + + apzc->AssertStateIsReset(); +} + +class APZCLongPressTester : public APZCGestureDetectorTester { +protected: + void DoLongPressTest(uint32_t aBehavior) { + MakeApzcUnzoomable(); + + uint64_t blockId = 0; + + nsEventStatus status = TouchDown(apzc, 10, 10, mcc->Time(), &blockId); + EXPECT_EQ(nsEventStatus_eConsumeDoDefault, status); + + if (gfxPrefs::TouchActionEnabled() && status != nsEventStatus_eConsumeNoDefault) { + // SetAllowedTouchBehavior() must be called after sending touch-start. + nsTArray<uint32_t> allowedTouchBehaviors; + allowedTouchBehaviors.AppendElement(aBehavior); + apzc->SetAllowedTouchBehavior(blockId, allowedTouchBehaviors); + } + // Have content "respond" to the touchstart + apzc->ContentReceivedInputBlock(blockId, false); + + MockFunction<void(std::string checkPointName)> check; + + { + InSequence s; + + EXPECT_CALL(check, Call("preHandleLongTap")); + blockId++; + EXPECT_CALL(*mcc, HandleLongTap(CSSPoint(10, 10), 0, apzc->GetGuid(), blockId)).Times(1); + EXPECT_CALL(check, Call("postHandleLongTap")); + + EXPECT_CALL(check, Call("preHandleSingleTap")); + EXPECT_CALL(*mcc, HandleSingleTap(CSSPoint(10, 10), 0, apzc->GetGuid())).Times(1); + EXPECT_CALL(check, Call("postHandleSingleTap")); + } + + // Manually invoke the longpress while the touch is currently down. + check.Call("preHandleLongTap"); + mcc->RunThroughDelayedTasks(); + check.Call("postHandleLongTap"); + + // Dispatching the longpress event starts a new touch block, which + // needs a new content response and also has a pending timeout task + // in the queue. Deal with those here. We do the content response first + // with preventDefault=false, and then we run the timeout task which + // "loses the race" and does nothing. + apzc->ContentReceivedInputBlock(blockId, false); + mcc->AdvanceByMillis(1000); + + // Finally, simulate lifting the finger. Since the long-press wasn't + // prevent-defaulted, we should get a long-tap-up event. + check.Call("preHandleSingleTap"); + status = TouchUp(apzc, 10, 10, mcc->Time()); + mcc->RunThroughDelayedTasks(); + EXPECT_EQ(nsEventStatus_eConsumeDoDefault, status); + check.Call("postHandleSingleTap"); + + apzc->AssertStateIsReset(); + } + + void DoLongPressPreventDefaultTest(uint32_t aBehavior) { + MakeApzcUnzoomable(); + + EXPECT_CALL(*mcc, RequestContentRepaint(_)).Times(0); + + int touchX = 10, + touchStartY = 10, + touchEndY = 50; + + uint64_t blockId = 0; + nsEventStatus status = TouchDown(apzc, touchX, touchStartY, mcc->Time(), &blockId); + EXPECT_EQ(nsEventStatus_eConsumeDoDefault, status); + + if (gfxPrefs::TouchActionEnabled() && status != nsEventStatus_eConsumeNoDefault) { + // SetAllowedTouchBehavior() must be called after sending touch-start. + nsTArray<uint32_t> allowedTouchBehaviors; + allowedTouchBehaviors.AppendElement(aBehavior); + apzc->SetAllowedTouchBehavior(blockId, allowedTouchBehaviors); + } + // Have content "respond" to the touchstart + apzc->ContentReceivedInputBlock(blockId, false); + + MockFunction<void(std::string checkPointName)> check; + + { + InSequence s; + + EXPECT_CALL(check, Call("preHandleLongTap")); + blockId++; + EXPECT_CALL(*mcc, HandleLongTap(CSSPoint(touchX, touchStartY), 0, apzc->GetGuid(), blockId)).Times(1); + EXPECT_CALL(check, Call("postHandleLongTap")); + } + + // Manually invoke the longpress while the touch is currently down. + check.Call("preHandleLongTap"); + mcc->RunThroughDelayedTasks(); + check.Call("postHandleLongTap"); + + // There should be a TimeoutContentResponse task in the queue still, + // waiting for the response from the longtap event dispatched above. + // Send the signal that content has handled the long-tap, and then run + // the timeout task (it will be a no-op because the content "wins" the + // race. This takes the place of the "contextmenu" event. + apzc->ContentReceivedInputBlock(blockId, true); + mcc->AdvanceByMillis(1000); + + MultiTouchInput mti = CreateMultiTouchInput(MultiTouchInput::MULTITOUCH_MOVE, mcc->Time()); + mti.mTouches.AppendElement(SingleTouchData(0, ParentLayerPoint(touchX, touchEndY), ScreenSize(0, 0), 0, 0)); + status = apzc->ReceiveInputEvent(mti, nullptr); + EXPECT_EQ(nsEventStatus_eConsumeDoDefault, status); + + EXPECT_CALL(*mcc, HandleSingleTap(CSSPoint(touchX, touchEndY), 0, apzc->GetGuid())).Times(0); + status = TouchUp(apzc, touchX, touchEndY, mcc->Time()); + EXPECT_EQ(nsEventStatus_eConsumeDoDefault, status); + + ParentLayerPoint pointOut; + AsyncTransform viewTransformOut; + apzc->SampleContentTransformForFrame(&viewTransformOut, pointOut); + + EXPECT_EQ(ParentLayerPoint(), pointOut); + EXPECT_EQ(AsyncTransform(), viewTransformOut); + + apzc->AssertStateIsReset(); + } +}; + +TEST_F(APZCLongPressTester, LongPress) { + SCOPED_GFX_PREF(TouchActionEnabled, bool, false); + DoLongPressTest(mozilla::layers::AllowedTouchBehavior::NONE); +} + +TEST_F(APZCLongPressTester, LongPressWithTouchAction) { + SCOPED_GFX_PREF(TouchActionEnabled, bool, true); + DoLongPressTest(mozilla::layers::AllowedTouchBehavior::HORIZONTAL_PAN + | mozilla::layers::AllowedTouchBehavior::VERTICAL_PAN + | mozilla::layers::AllowedTouchBehavior::PINCH_ZOOM); +} + +TEST_F(APZCLongPressTester, LongPressPreventDefault) { + SCOPED_GFX_PREF(TouchActionEnabled, bool, false); + DoLongPressPreventDefaultTest(mozilla::layers::AllowedTouchBehavior::NONE); +} + +TEST_F(APZCLongPressTester, LongPressPreventDefaultWithTouchAction) { + SCOPED_GFX_PREF(TouchActionEnabled, bool, true); + DoLongPressPreventDefaultTest(mozilla::layers::AllowedTouchBehavior::HORIZONTAL_PAN + | mozilla::layers::AllowedTouchBehavior::VERTICAL_PAN + | mozilla::layers::AllowedTouchBehavior::PINCH_ZOOM); +} + +TEST_F(APZCGestureDetectorTester, DoubleTap) { + MakeApzcWaitForMainThread(); + MakeApzcZoomable(); + + EXPECT_CALL(*mcc, HandleSingleTap(CSSPoint(10, 10), 0, apzc->GetGuid())).Times(0); + EXPECT_CALL(*mcc, HandleDoubleTap(CSSPoint(10, 10), 0, apzc->GetGuid())).Times(1); + + uint64_t blockIds[2]; + DoubleTapAndCheckStatus(apzc, 10, 10, mcc, &blockIds); + + // responses to the two touchstarts + apzc->ContentReceivedInputBlock(blockIds[0], false); + apzc->ContentReceivedInputBlock(blockIds[1], false); + + apzc->AssertStateIsReset(); +} + +TEST_F(APZCGestureDetectorTester, DoubleTapNotZoomable) { + MakeApzcWaitForMainThread(); + MakeApzcUnzoomable(); + + EXPECT_CALL(*mcc, HandleSingleTap(CSSPoint(10, 10), 0, apzc->GetGuid())).Times(2); + EXPECT_CALL(*mcc, HandleDoubleTap(CSSPoint(10, 10), 0, apzc->GetGuid())).Times(0); + + uint64_t blockIds[2]; + DoubleTapAndCheckStatus(apzc, 10, 10, mcc, &blockIds); + + // responses to the two touchstarts + apzc->ContentReceivedInputBlock(blockIds[0], false); + apzc->ContentReceivedInputBlock(blockIds[1], false); + + apzc->AssertStateIsReset(); +} + +TEST_F(APZCGestureDetectorTester, DoubleTapPreventDefaultFirstOnly) { + MakeApzcWaitForMainThread(); + MakeApzcZoomable(); + + EXPECT_CALL(*mcc, HandleSingleTap(CSSPoint(10, 10), 0, apzc->GetGuid())).Times(1); + EXPECT_CALL(*mcc, HandleDoubleTap(CSSPoint(10, 10), 0, apzc->GetGuid())).Times(0); + + uint64_t blockIds[2]; + DoubleTapAndCheckStatus(apzc, 10, 10, mcc, &blockIds); + + // responses to the two touchstarts + apzc->ContentReceivedInputBlock(blockIds[0], true); + apzc->ContentReceivedInputBlock(blockIds[1], false); + + apzc->AssertStateIsReset(); +} + +TEST_F(APZCGestureDetectorTester, DoubleTapPreventDefaultBoth) { + MakeApzcWaitForMainThread(); + MakeApzcZoomable(); + + EXPECT_CALL(*mcc, HandleSingleTap(CSSPoint(10, 10), 0, apzc->GetGuid())).Times(0); + EXPECT_CALL(*mcc, HandleDoubleTap(CSSPoint(10, 10), 0, apzc->GetGuid())).Times(0); + + uint64_t blockIds[2]; + DoubleTapAndCheckStatus(apzc, 10, 10, mcc, &blockIds); + + // responses to the two touchstarts + apzc->ContentReceivedInputBlock(blockIds[0], true); + apzc->ContentReceivedInputBlock(blockIds[1], true); + + apzc->AssertStateIsReset(); +} + +// Test for bug 947892 +// We test whether we dispatch tap event when the tap is followed by pinch. +TEST_F(APZCGestureDetectorTester, TapFollowedByPinch) { + MakeApzcZoomable(); + + EXPECT_CALL(*mcc, HandleSingleTap(CSSPoint(10, 10), 0, apzc->GetGuid())).Times(1); + + Tap(apzc, 10, 10, mcc, TimeDuration::FromMilliseconds(100)); + + int inputId = 0; + MultiTouchInput mti; + mti = CreateMultiTouchInput(MultiTouchInput::MULTITOUCH_START, mcc->Time()); + mti.mTouches.AppendElement(SingleTouchData(inputId, ParentLayerPoint(20, 20), ScreenSize(0, 0), 0, 0)); + mti.mTouches.AppendElement(SingleTouchData(inputId + 1, ParentLayerPoint(10, 10), ScreenSize(0, 0), 0, 0)); + apzc->ReceiveInputEvent(mti, nullptr); + + mti = CreateMultiTouchInput(MultiTouchInput::MULTITOUCH_END, mcc->Time()); + mti.mTouches.AppendElement(SingleTouchData(inputId, ParentLayerPoint(20, 20), ScreenSize(0, 0), 0, 0)); + mti.mTouches.AppendElement(SingleTouchData(inputId + 1, ParentLayerPoint(10, 10), ScreenSize(0, 0), 0, 0)); + apzc->ReceiveInputEvent(mti, nullptr); + + apzc->AssertStateIsReset(); +} + +TEST_F(APZCGestureDetectorTester, TapFollowedByMultipleTouches) { + MakeApzcZoomable(); + + EXPECT_CALL(*mcc, HandleSingleTap(CSSPoint(10, 10), 0, apzc->GetGuid())).Times(1); + + Tap(apzc, 10, 10, mcc, TimeDuration::FromMilliseconds(100)); + + int inputId = 0; + MultiTouchInput mti; + mti = CreateMultiTouchInput(MultiTouchInput::MULTITOUCH_START, mcc->Time()); + mti.mTouches.AppendElement(SingleTouchData(inputId, ParentLayerPoint(20, 20), ScreenSize(0, 0), 0, 0)); + apzc->ReceiveInputEvent(mti, nullptr); + + mti = CreateMultiTouchInput(MultiTouchInput::MULTITOUCH_START, mcc->Time()); + mti.mTouches.AppendElement(SingleTouchData(inputId, ParentLayerPoint(20, 20), ScreenSize(0, 0), 0, 0)); + mti.mTouches.AppendElement(SingleTouchData(inputId + 1, ParentLayerPoint(10, 10), ScreenSize(0, 0), 0, 0)); + apzc->ReceiveInputEvent(mti, nullptr); + + mti = CreateMultiTouchInput(MultiTouchInput::MULTITOUCH_END, mcc->Time()); + mti.mTouches.AppendElement(SingleTouchData(inputId, ParentLayerPoint(20, 20), ScreenSize(0, 0), 0, 0)); + mti.mTouches.AppendElement(SingleTouchData(inputId + 1, ParentLayerPoint(10, 10), ScreenSize(0, 0), 0, 0)); + apzc->ReceiveInputEvent(mti, nullptr); + + apzc->AssertStateIsReset(); +} +
new file mode 100644 --- /dev/null +++ b/gfx/layers/apz/test/gtest/TestHitTesting.cpp @@ -0,0 +1,487 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set sw=2 ts=8 et 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 "APZCTreeManagerTester.h" +#include "APZTestCommon.h" +#include "InputUtils.h" + +class APZHitTestingTester : public APZCTreeManagerTester { +protected: + ScreenToParentLayerMatrix4x4 transformToApzc; + ParentLayerToScreenMatrix4x4 transformToGecko; + + already_AddRefed<AsyncPanZoomController> GetTargetAPZC(const ScreenPoint& aPoint) { + RefPtr<AsyncPanZoomController> hit = manager->GetTargetAPZC(aPoint, nullptr); + if (hit) { + transformToApzc = manager->GetScreenToApzcTransform(hit.get()); + transformToGecko = manager->GetApzcToGeckoTransform(hit.get()); + } + return hit.forget(); + } + +protected: + void CreateHitTesting1LayerTree() { + const char* layerTreeSyntax = "c(tttt)"; + // LayerID 0 1234 + nsIntRegion layerVisibleRegion[] = { + nsIntRegion(IntRect(0,0,100,100)), + nsIntRegion(IntRect(0,0,100,100)), + nsIntRegion(IntRect(10,10,20,20)), + nsIntRegion(IntRect(10,10,20,20)), + nsIntRegion(IntRect(5,5,20,20)), + }; + root = CreateLayerTree(layerTreeSyntax, layerVisibleRegion, nullptr, lm, layers); + } + + void CreateHitTesting2LayerTree() { + const char* layerTreeSyntax = "c(tc(t))"; + // LayerID 0 12 3 + nsIntRegion layerVisibleRegion[] = { + nsIntRegion(IntRect(0,0,100,100)), + nsIntRegion(IntRect(10,10,40,40)), + nsIntRegion(IntRect(10,60,40,40)), + nsIntRegion(IntRect(10,60,40,40)), + }; + Matrix4x4 transforms[] = { + Matrix4x4(), + Matrix4x4(), + Matrix4x4::Scaling(2, 1, 1), + Matrix4x4(), + }; + root = CreateLayerTree(layerTreeSyntax, layerVisibleRegion, transforms, lm, layers); + + SetScrollableFrameMetrics(root, FrameMetrics::START_SCROLL_ID, CSSRect(0, 0, 200, 200)); + SetScrollableFrameMetrics(layers[1], FrameMetrics::START_SCROLL_ID + 1, CSSRect(0, 0, 80, 80)); + SetScrollableFrameMetrics(layers[3], FrameMetrics::START_SCROLL_ID + 2, CSSRect(0, 0, 80, 80)); + } + + void CreateComplexMultiLayerTree() { + const char* layerTreeSyntax = "c(tc(t)tc(c(t)tt))"; + // LayerID 0 12 3 45 6 7 89 + nsIntRegion layerVisibleRegion[] = { + nsIntRegion(IntRect(0,0,300,400)), // root(0) + nsIntRegion(IntRect(0,0,100,100)), // thebes(1) in top-left + nsIntRegion(IntRect(50,50,200,300)), // container(2) centered in root(0) + nsIntRegion(IntRect(50,50,200,300)), // thebes(3) fully occupying parent container(2) + nsIntRegion(IntRect(0,200,100,100)), // thebes(4) in bottom-left + nsIntRegion(IntRect(200,0,100,400)), // container(5) along the right 100px of root(0) + nsIntRegion(IntRect(200,0,100,200)), // container(6) taking up the top half of parent container(5) + nsIntRegion(IntRect(200,0,100,200)), // thebes(7) fully occupying parent container(6) + nsIntRegion(IntRect(200,200,100,100)), // thebes(8) in bottom-right (below (6)) + nsIntRegion(IntRect(200,300,100,100)), // thebes(9) in bottom-right (below (8)) + }; + root = CreateLayerTree(layerTreeSyntax, layerVisibleRegion, nullptr, lm, layers); + SetScrollableFrameMetrics(layers[1], FrameMetrics::START_SCROLL_ID); + SetScrollableFrameMetrics(layers[2], FrameMetrics::START_SCROLL_ID); + SetScrollableFrameMetrics(layers[4], FrameMetrics::START_SCROLL_ID + 1); + SetScrollableFrameMetrics(layers[6], FrameMetrics::START_SCROLL_ID + 1); + SetScrollableFrameMetrics(layers[7], FrameMetrics::START_SCROLL_ID + 2); + SetScrollableFrameMetrics(layers[8], FrameMetrics::START_SCROLL_ID + 1); + SetScrollableFrameMetrics(layers[9], FrameMetrics::START_SCROLL_ID + 3); + } + + void CreateBug1148350LayerTree() { + const char* layerTreeSyntax = "c(t)"; + // LayerID 0 1 + nsIntRegion layerVisibleRegion[] = { + nsIntRegion(IntRect(0,0,200,200)), + nsIntRegion(IntRect(0,0,200,200)), + }; + root = CreateLayerTree(layerTreeSyntax, layerVisibleRegion, nullptr, lm, layers); + SetScrollableFrameMetrics(layers[1], FrameMetrics::START_SCROLL_ID); + } +}; + +// A simple hit testing test that doesn't involve any transforms on layers. +TEST_F(APZHitTestingTester, HitTesting1) { + CreateHitTesting1LayerTree(); + ScopedLayerTreeRegistration registration(manager, 0, root, mcc); + + // No APZC attached so hit testing will return no APZC at (20,20) + RefPtr<AsyncPanZoomController> hit = GetTargetAPZC(ScreenPoint(20, 20)); + TestAsyncPanZoomController* nullAPZC = nullptr; + EXPECT_EQ(nullAPZC, hit.get()); + EXPECT_EQ(ScreenToParentLayerMatrix4x4(), transformToApzc); + EXPECT_EQ(ParentLayerToScreenMatrix4x4(), transformToGecko); + + uint32_t paintSequenceNumber = 0; + + // Now we have a root APZC that will match the page + SetScrollableFrameMetrics(root, FrameMetrics::START_SCROLL_ID); + manager->UpdateHitTestingTree(nullptr, root, false, 0, paintSequenceNumber++); + hit = GetTargetAPZC(ScreenPoint(15, 15)); + EXPECT_EQ(ApzcOf(root), hit.get()); + // expect hit point at LayerIntPoint(15, 15) + EXPECT_EQ(ParentLayerPoint(15, 15), transformToApzc * ScreenPoint(15, 15)); + EXPECT_EQ(ScreenPoint(15, 15), transformToGecko * ParentLayerPoint(15, 15)); + + // Now we have a sub APZC with a better fit + SetScrollableFrameMetrics(layers[3], FrameMetrics::START_SCROLL_ID + 1); + manager->UpdateHitTestingTree(nullptr, root, false, 0, paintSequenceNumber++); + EXPECT_NE(ApzcOf(root), ApzcOf(layers[3])); + hit = GetTargetAPZC(ScreenPoint(25, 25)); + EXPECT_EQ(ApzcOf(layers[3]), hit.get()); + // expect hit point at LayerIntPoint(25, 25) + EXPECT_EQ(ParentLayerPoint(25, 25), transformToApzc * ScreenPoint(25, 25)); + EXPECT_EQ(ScreenPoint(25, 25), transformToGecko * ParentLayerPoint(25, 25)); + + // At this point, layers[4] obscures layers[3] at the point (15, 15) so + // hitting there should hit the root APZC + hit = GetTargetAPZC(ScreenPoint(15, 15)); + EXPECT_EQ(ApzcOf(root), hit.get()); + + // Now test hit testing when we have two scrollable layers + SetScrollableFrameMetrics(layers[4], FrameMetrics::START_SCROLL_ID + 2); + manager->UpdateHitTestingTree(nullptr, root, false, 0, paintSequenceNumber++); + hit = GetTargetAPZC(ScreenPoint(15, 15)); + EXPECT_EQ(ApzcOf(layers[4]), hit.get()); + // expect hit point at LayerIntPoint(15, 15) + EXPECT_EQ(ParentLayerPoint(15, 15), transformToApzc * ScreenPoint(15, 15)); + EXPECT_EQ(ScreenPoint(15, 15), transformToGecko * ParentLayerPoint(15, 15)); + + // Hit test ouside the reach of layer[3,4] but inside root + hit = GetTargetAPZC(ScreenPoint(90, 90)); + EXPECT_EQ(ApzcOf(root), hit.get()); + // expect hit point at LayerIntPoint(90, 90) + EXPECT_EQ(ParentLayerPoint(90, 90), transformToApzc * ScreenPoint(90, 90)); + EXPECT_EQ(ScreenPoint(90, 90), transformToGecko * ParentLayerPoint(90, 90)); + + // Hit test ouside the reach of any layer + hit = GetTargetAPZC(ScreenPoint(1000, 10)); + EXPECT_EQ(nullAPZC, hit.get()); + EXPECT_EQ(ScreenToParentLayerMatrix4x4(), transformToApzc); + EXPECT_EQ(ParentLayerToScreenMatrix4x4(), transformToGecko); + hit = GetTargetAPZC(ScreenPoint(-1000, 10)); + EXPECT_EQ(nullAPZC, hit.get()); + EXPECT_EQ(ScreenToParentLayerMatrix4x4(), transformToApzc); + EXPECT_EQ(ParentLayerToScreenMatrix4x4(), transformToGecko); +} + +// A more involved hit testing test that involves css and async transforms. +TEST_F(APZHitTestingTester, HitTesting2) { + CreateHitTesting2LayerTree(); + ScopedLayerTreeRegistration registration(manager, 0, root, mcc); + + manager->UpdateHitTestingTree(nullptr, root, false, 0, 0); + + // At this point, the following holds (all coordinates in screen pixels): + // layers[0] has content from (0,0)-(200,200), clipped by composition bounds (0,0)-(100,100) + // layers[1] has content from (10,10)-(90,90), clipped by composition bounds (10,10)-(50,50) + // layers[2] has content from (20,60)-(100,100). no clipping as it's not a scrollable layer + // layers[3] has content from (20,60)-(180,140), clipped by composition bounds (20,60)-(100,100) + + TestAsyncPanZoomController* apzcroot = ApzcOf(root); + TestAsyncPanZoomController* apzc1 = ApzcOf(layers[1]); + TestAsyncPanZoomController* apzc3 = ApzcOf(layers[3]); + + // Hit an area that's clearly on the root layer but not any of the child layers. + RefPtr<AsyncPanZoomController> hit = GetTargetAPZC(ScreenPoint(75, 25)); + EXPECT_EQ(apzcroot, hit.get()); + EXPECT_EQ(ParentLayerPoint(75, 25), transformToApzc * ScreenPoint(75, 25)); + EXPECT_EQ(ScreenPoint(75, 25), transformToGecko * ParentLayerPoint(75, 25)); + + // Hit an area on the root that would be on layers[3] if layers[2] + // weren't transformed. + // Note that if layers[2] were scrollable, then this would hit layers[2] + // because its composition bounds would be at (10,60)-(50,100) (and the + // scale-only transform that we set on layers[2] would be invalid because + // it would place the layer into overscroll, as its composition bounds + // start at x=10 but its content at x=20). + hit = GetTargetAPZC(ScreenPoint(15, 75)); + EXPECT_EQ(apzcroot, hit.get()); + EXPECT_EQ(ParentLayerPoint(15, 75), transformToApzc * ScreenPoint(15, 75)); + EXPECT_EQ(ScreenPoint(15, 75), transformToGecko * ParentLayerPoint(15, 75)); + + // Hit an area on layers[1]. + hit = GetTargetAPZC(ScreenPoint(25, 25)); + EXPECT_EQ(apzc1, hit.get()); + EXPECT_EQ(ParentLayerPoint(25, 25), transformToApzc * ScreenPoint(25, 25)); + EXPECT_EQ(ScreenPoint(25, 25), transformToGecko * ParentLayerPoint(25, 25)); + + // Hit an area on layers[3]. + hit = GetTargetAPZC(ScreenPoint(25, 75)); + EXPECT_EQ(apzc3, hit.get()); + // transformToApzc should unapply layers[2]'s transform + EXPECT_EQ(ParentLayerPoint(12.5, 75), transformToApzc * ScreenPoint(25, 75)); + // and transformToGecko should reapply it + EXPECT_EQ(ScreenPoint(25, 75), transformToGecko * ParentLayerPoint(12.5, 75)); + + // Hit an area on layers[3] that would be on the root if layers[2] + // weren't transformed. + hit = GetTargetAPZC(ScreenPoint(75, 75)); + EXPECT_EQ(apzc3, hit.get()); + // transformToApzc should unapply layers[2]'s transform + EXPECT_EQ(ParentLayerPoint(37.5, 75), transformToApzc * ScreenPoint(75, 75)); + // and transformToGecko should reapply it + EXPECT_EQ(ScreenPoint(75, 75), transformToGecko * ParentLayerPoint(37.5, 75)); + + // Pan the root layer upward by 50 pixels. + // This causes layers[1] to scroll out of view, and an async transform + // of -50 to be set on the root layer. + EXPECT_CALL(*mcc, RequestContentRepaint(_)).Times(1); + + // This first pan will move the APZC by 50 pixels, and dispatch a paint request. + // Since this paint request is in the queue to Gecko, transformToGecko will + // take it into account. + ApzcPanNoFling(apzcroot, mcc, 100, 50); + + // Hit where layers[3] used to be. It should now hit the root. + hit = GetTargetAPZC(ScreenPoint(75, 75)); + EXPECT_EQ(apzcroot, hit.get()); + // transformToApzc doesn't unapply the root's own async transform + EXPECT_EQ(ParentLayerPoint(75, 75), transformToApzc * ScreenPoint(75, 75)); + // and transformToGecko unapplies it and then reapplies it, because by the + // time the event being transformed reaches Gecko the new paint request will + // have been handled. + EXPECT_EQ(ScreenPoint(75, 75), transformToGecko * ParentLayerPoint(75, 75)); + + // Hit where layers[1] used to be and where layers[3] should now be. + hit = GetTargetAPZC(ScreenPoint(25, 25)); + EXPECT_EQ(apzc3, hit.get()); + // transformToApzc unapplies both layers[2]'s css transform and the root's + // async transform + EXPECT_EQ(ParentLayerPoint(12.5, 75), transformToApzc * ScreenPoint(25, 25)); + // transformToGecko reapplies both the css transform and the async transform + // because we have already issued a paint request with it. + EXPECT_EQ(ScreenPoint(25, 25), transformToGecko * ParentLayerPoint(12.5, 75)); + + // This second pan will move the APZC by another 50 pixels. + EXPECT_CALL(*mcc, RequestContentRepaint(_)).Times(1); + ApzcPanNoFling(apzcroot, mcc, 100, 50); + + // Hit where layers[3] used to be. It should now hit the root. + hit = GetTargetAPZC(ScreenPoint(75, 75)); + EXPECT_EQ(apzcroot, hit.get()); + // transformToApzc doesn't unapply the root's own async transform + EXPECT_EQ(ParentLayerPoint(75, 75), transformToApzc * ScreenPoint(75, 75)); + // transformToGecko unapplies the full async transform of -100 pixels + EXPECT_EQ(ScreenPoint(75, 75), transformToGecko * ParentLayerPoint(75, 75)); + + // Hit where layers[1] used to be. It should now hit the root. + hit = GetTargetAPZC(ScreenPoint(25, 25)); + EXPECT_EQ(apzcroot, hit.get()); + // transformToApzc doesn't unapply the root's own async transform + EXPECT_EQ(ParentLayerPoint(25, 25), transformToApzc * ScreenPoint(25, 25)); + // transformToGecko unapplies the full async transform of -100 pixels + EXPECT_EQ(ScreenPoint(25, 25), transformToGecko * ParentLayerPoint(25, 25)); +} + +TEST_F(APZHitTestingTester, ComplexMultiLayerTree) { + CreateComplexMultiLayerTree(); + ScopedLayerTreeRegistration registration(manager, 0, root, mcc); + manager->UpdateHitTestingTree(nullptr, root, false, 0, 0); + + /* The layer tree looks like this: + + 0 + |----|--+--|----| + 1 2 4 5 + | /|\ + 3 6 8 9 + | + 7 + + Layers 1,2 have the same APZC + Layers 4,6,8 have the same APZC + Layer 7 has an APZC + Layer 9 has an APZC + */ + + TestAsyncPanZoomController* nullAPZC = nullptr; + // Ensure all the scrollable layers have an APZC + EXPECT_FALSE(layers[0]->HasScrollableFrameMetrics()); + EXPECT_NE(nullAPZC, ApzcOf(layers[1])); + EXPECT_NE(nullAPZC, ApzcOf(layers[2])); + EXPECT_FALSE(layers[3]->HasScrollableFrameMetrics()); + EXPECT_NE(nullAPZC, ApzcOf(layers[4])); + EXPECT_FALSE(layers[5]->HasScrollableFrameMetrics()); + EXPECT_NE(nullAPZC, ApzcOf(layers[6])); + EXPECT_NE(nullAPZC, ApzcOf(layers[7])); + EXPECT_NE(nullAPZC, ApzcOf(layers[8])); + EXPECT_NE(nullAPZC, ApzcOf(layers[9])); + // Ensure those that scroll together have the same APZCs + EXPECT_EQ(ApzcOf(layers[1]), ApzcOf(layers[2])); + EXPECT_EQ(ApzcOf(layers[4]), ApzcOf(layers[6])); + EXPECT_EQ(ApzcOf(layers[8]), ApzcOf(layers[6])); + // Ensure those that don't scroll together have different APZCs + EXPECT_NE(ApzcOf(layers[1]), ApzcOf(layers[4])); + EXPECT_NE(ApzcOf(layers[1]), ApzcOf(layers[7])); + EXPECT_NE(ApzcOf(layers[1]), ApzcOf(layers[9])); + EXPECT_NE(ApzcOf(layers[4]), ApzcOf(layers[7])); + EXPECT_NE(ApzcOf(layers[4]), ApzcOf(layers[9])); + EXPECT_NE(ApzcOf(layers[7]), ApzcOf(layers[9])); + // Ensure the APZC parent chains are set up correctly + TestAsyncPanZoomController* layers1_2 = ApzcOf(layers[1]); + TestAsyncPanZoomController* layers4_6_8 = ApzcOf(layers[4]); + TestAsyncPanZoomController* layer7 = ApzcOf(layers[7]); + TestAsyncPanZoomController* layer9 = ApzcOf(layers[9]); + EXPECT_EQ(nullptr, layers1_2->GetParent()); + EXPECT_EQ(nullptr, layers4_6_8->GetParent()); + EXPECT_EQ(layers4_6_8, layer7->GetParent()); + EXPECT_EQ(nullptr, layer9->GetParent()); + // Ensure the hit-testing tree looks like the layer tree + RefPtr<HitTestingTreeNode> root = manager->GetRootNode(); + RefPtr<HitTestingTreeNode> node5 = root->GetLastChild(); + RefPtr<HitTestingTreeNode> node4 = node5->GetPrevSibling(); + RefPtr<HitTestingTreeNode> node2 = node4->GetPrevSibling(); + RefPtr<HitTestingTreeNode> node1 = node2->GetPrevSibling(); + RefPtr<HitTestingTreeNode> node3 = node2->GetLastChild(); + RefPtr<HitTestingTreeNode> node9 = node5->GetLastChild(); + RefPtr<HitTestingTreeNode> node8 = node9->GetPrevSibling(); + RefPtr<HitTestingTreeNode> node6 = node8->GetPrevSibling(); + RefPtr<HitTestingTreeNode> node7 = node6->GetLastChild(); + EXPECT_EQ(nullptr, node1->GetPrevSibling()); + EXPECT_EQ(nullptr, node3->GetPrevSibling()); + EXPECT_EQ(nullptr, node6->GetPrevSibling()); + EXPECT_EQ(nullptr, node7->GetPrevSibling()); + EXPECT_EQ(nullptr, node1->GetLastChild()); + EXPECT_EQ(nullptr, node3->GetLastChild()); + EXPECT_EQ(nullptr, node4->GetLastChild()); + EXPECT_EQ(nullptr, node7->GetLastChild()); + EXPECT_EQ(nullptr, node8->GetLastChild()); + EXPECT_EQ(nullptr, node9->GetLastChild()); + + RefPtr<AsyncPanZoomController> hit = GetTargetAPZC(ScreenPoint(25, 25)); + EXPECT_EQ(ApzcOf(layers[1]), hit.get()); + hit = GetTargetAPZC(ScreenPoint(275, 375)); + EXPECT_EQ(ApzcOf(layers[9]), hit.get()); + hit = GetTargetAPZC(ScreenPoint(250, 100)); + EXPECT_EQ(ApzcOf(layers[7]), hit.get()); +} + +TEST_F(APZHitTestingTester, TestRepaintFlushOnNewInputBlock) { + SCOPED_GFX_PREF(TouchActionEnabled, bool, false); + + // The main purpose of this test is to verify that touch-start events (or anything + // that starts a new input block) don't ever get untransformed. This should always + // hold because the APZ code should flush repaints when we start a new input block + // and the transform to gecko space should be empty. + + CreateSimpleScrollingLayer(); + ScopedLayerTreeRegistration registration(manager, 0, root, mcc); + manager->UpdateHitTestingTree(nullptr, root, false, 0, 0); + TestAsyncPanZoomController* apzcroot = ApzcOf(root); + + // At this point, the following holds (all coordinates in screen pixels): + // layers[0] has content from (0,0)-(500,500), clipped by composition bounds (0,0)-(200,200) + + MockFunction<void(std::string checkPointName)> check; + + { + InSequence s; + + EXPECT_CALL(*mcc, RequestContentRepaint(_)).Times(AtLeast(1)); + EXPECT_CALL(check, Call("post-first-touch-start")); + EXPECT_CALL(*mcc, RequestContentRepaint(_)).Times(AtLeast(1)); + EXPECT_CALL(check, Call("post-second-fling")); + EXPECT_CALL(*mcc, RequestContentRepaint(_)).Times(AtLeast(1)); + EXPECT_CALL(check, Call("post-second-touch-start")); + } + + // This first pan will move the APZC by 50 pixels, and dispatch a paint request. + ApzcPanNoFling(apzcroot, mcc, 100, 50); + + // Verify that a touch start doesn't get untransformed + ScreenIntPoint touchPoint(50, 50); + MultiTouchInput mti = CreateMultiTouchInput(MultiTouchInput::MULTITOUCH_START, mcc->Time()); + mti.mTouches.AppendElement(SingleTouchData(0, touchPoint, ScreenSize(0, 0), 0, 0)); + + EXPECT_EQ(nsEventStatus_eConsumeDoDefault, manager->ReceiveInputEvent(mti, nullptr, nullptr)); + EXPECT_EQ(touchPoint, mti.mTouches[0].mScreenPoint); + check.Call("post-first-touch-start"); + + // Send a touchend to clear state + mti.mType = MultiTouchInput::MULTITOUCH_END; + manager->ReceiveInputEvent(mti, nullptr, nullptr); + + mcc->AdvanceByMillis(1000); + + // Now do two pans. The first of these will dispatch a repaint request, as above. + // The second will get stuck in the paint throttler because the first one doesn't + // get marked as "completed", so this will result in a non-empty LD transform. + // (Note that any outstanding repaint requests from the first half of this test + // don't impact this half because we advance the time by 1 second, which will trigger + // the max-wait-exceeded codepath in the paint throttler). + ApzcPanNoFling(apzcroot, mcc, 100, 50); + check.Call("post-second-fling"); + ApzcPanNoFling(apzcroot, mcc, 100, 50); + + // Ensure that a touch start again doesn't get untransformed by flushing + // a repaint + mti.mType = MultiTouchInput::MULTITOUCH_START; + EXPECT_EQ(nsEventStatus_eConsumeDoDefault, manager->ReceiveInputEvent(mti, nullptr, nullptr)); + EXPECT_EQ(touchPoint, mti.mTouches[0].mScreenPoint); + check.Call("post-second-touch-start"); + + mti.mType = MultiTouchInput::MULTITOUCH_END; + EXPECT_EQ(nsEventStatus_eConsumeDoDefault, manager->ReceiveInputEvent(mti, nullptr, nullptr)); + EXPECT_EQ(touchPoint, mti.mTouches[0].mScreenPoint); +} + +TEST_F(APZHitTestingTester, TestRepaintFlushOnWheelEvents) { + // The purpose of this test is to ensure that wheel events trigger a repaint + // flush as per bug 1166871, and that the wheel event untransform is a no-op. + + CreateSimpleScrollingLayer(); + ScopedLayerTreeRegistration registration(manager, 0, root, mcc); + manager->UpdateHitTestingTree(nullptr, root, false, 0, 0); + TestAsyncPanZoomController* apzcroot = ApzcOf(root); + + EXPECT_CALL(*mcc, RequestContentRepaint(_)).Times(AtLeast(3)); + ScreenPoint origin(100, 50); + for (int i = 0; i < 3; i++) { + ScrollWheelInput swi(MillisecondsSinceStartup(mcc->Time()), mcc->Time(), 0, + ScrollWheelInput::SCROLLMODE_INSTANT, ScrollWheelInput::SCROLLDELTA_PIXEL, + origin, 0, 10); + EXPECT_EQ(nsEventStatus_eConsumeDoDefault, manager->ReceiveInputEvent(swi, nullptr, nullptr)); + EXPECT_EQ(origin, swi.mOrigin); + + AsyncTransform viewTransform; + ParentLayerPoint point; + apzcroot->SampleContentTransformForFrame(&viewTransform, point); + EXPECT_EQ(0, point.x); + EXPECT_EQ((i + 1) * 10, point.y); + EXPECT_EQ(0, viewTransform.mTranslation.x); + EXPECT_EQ((i + 1) * -10, viewTransform.mTranslation.y); + + mcc->AdvanceByMillis(5); + } +} + +TEST_F(APZHitTestingTester, Bug1148350) { + CreateBug1148350LayerTree(); + ScopedLayerTreeRegistration registration(manager, 0, root, mcc); + manager->UpdateHitTestingTree(nullptr, root, false, 0, 0); + + MockFunction<void(std::string checkPointName)> check; + { + InSequence s; + EXPECT_CALL(*mcc, HandleSingleTap(CSSPoint(100, 100), 0, ApzcOf(layers[1])->GetGuid())).Times(1); + EXPECT_CALL(check, Call("Tapped without transform")); + EXPECT_CALL(*mcc, HandleSingleTap(CSSPoint(100, 100), 0, ApzcOf(layers[1])->GetGuid())).Times(1); + EXPECT_CALL(check, Call("Tapped with interleaved transform")); + } + + Tap(manager, 100, 100, mcc, TimeDuration::FromMilliseconds(100)); + mcc->RunThroughDelayedTasks(); + check.Call("Tapped without transform"); + + uint64_t blockId; + TouchDown(manager, 100, 100, mcc->Time(), &blockId); + if (gfxPrefs::TouchActionEnabled()) { + SetDefaultAllowedTouchBehavior(manager, blockId); + } + mcc->AdvanceByMillis(100); + + layers[0]->SetVisibleRegion(LayerIntRegion(LayerIntRect(0,50,200,150))); + layers[0]->SetBaseTransform(Matrix4x4::Translation(0, 50, 0)); + manager->UpdateHitTestingTree(nullptr, root, false, 0, 0); + + TouchUp(manager, 100, 100, mcc->Time()); + mcc->RunThroughDelayedTasks(); + check.Call("Tapped with interleaved transform"); +} +
new file mode 100644 --- /dev/null +++ b/gfx/layers/apz/test/gtest/TestOverscrollHandoff.cpp @@ -0,0 +1,430 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set sw=2 ts=8 et 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 "APZCTreeManagerTester.h" +#include "APZTestCommon.h" +#include "InputUtils.h" + +class APZOverscrollHandoffTester : public APZCTreeManagerTester { +protected: + UniquePtr<ScopedLayerTreeRegistration> registration; + TestAsyncPanZoomController* rootApzc; + + void CreateOverscrollHandoffLayerTree1() { + const char* layerTreeSyntax = "c(t)"; + nsIntRegion layerVisibleRegion[] = { + nsIntRegion(IntRect(0, 0, 100, 100)), + nsIntRegion(IntRect(0, 50, 100, 50)) + }; + root = CreateLayerTree(layerTreeSyntax, layerVisibleRegion, nullptr, lm, layers); + SetScrollableFrameMetrics(root, FrameMetrics::START_SCROLL_ID, CSSRect(0, 0, 200, 200)); + SetScrollableFrameMetrics(layers[1], FrameMetrics::START_SCROLL_ID + 1, CSSRect(0, 0, 100, 100)); + SetScrollHandoff(layers[1], root); + registration = MakeUnique<ScopedLayerTreeRegistration>(manager, 0, root, mcc); + manager->UpdateHitTestingTree(nullptr, root, false, 0, 0); + rootApzc = ApzcOf(root); + } + + void CreateOverscrollHandoffLayerTree2() { + const char* layerTreeSyntax = "c(c(t))"; + nsIntRegion layerVisibleRegion[] = { + nsIntRegion(IntRect(0, 0, 100, 100)), + nsIntRegion(IntRect(0, 0, 100, 100)), + nsIntRegion(IntRect(0, 50, 100, 50)) + }; + root = CreateLayerTree(layerTreeSyntax, layerVisibleRegion, nullptr, lm, layers); + SetScrollableFrameMetrics(root, FrameMetrics::START_SCROLL_ID, CSSRect(0, 0, 200, 200)); + SetScrollableFrameMetrics(layers[1], FrameMetrics::START_SCROLL_ID + 2, CSSRect(-100, -100, 200, 200)); + SetScrollableFrameMetrics(layers[2], FrameMetrics::START_SCROLL_ID + 1, CSSRect(0, 0, 100, 100)); + SetScrollHandoff(layers[1], root); + SetScrollHandoff(layers[2], layers[1]); + // No ScopedLayerTreeRegistration as that just needs to be done once per test + // and this is the second layer tree for a particular test. + MOZ_ASSERT(registration); + manager->UpdateHitTestingTree(nullptr, root, false, 0, 0); + rootApzc = ApzcOf(root); + } + + void CreateOverscrollHandoffLayerTree3() { + const char* layerTreeSyntax = "c(c(t)c(t))"; + nsIntRegion layerVisibleRegion[] = { + nsIntRegion(IntRect(0, 0, 100, 100)), // root + nsIntRegion(IntRect(0, 0, 100, 50)), // scrolling parent 1 + nsIntRegion(IntRect(0, 0, 100, 50)), // scrolling child 1 + nsIntRegion(IntRect(0, 50, 100, 50)), // scrolling parent 2 + nsIntRegion(IntRect(0, 50, 100, 50)) // scrolling child 2 + }; + root = CreateLayerTree(layerTreeSyntax, layerVisibleRegion, nullptr, lm, layers); + SetScrollableFrameMetrics(layers[1], FrameMetrics::START_SCROLL_ID, CSSRect(0, 0, 100, 100)); + SetScrollableFrameMetrics(layers[2], FrameMetrics::START_SCROLL_ID + 1, CSSRect(0, 0, 100, 100)); + SetScrollableFrameMetrics(layers[3], FrameMetrics::START_SCROLL_ID + 2, CSSRect(0, 50, 100, 100)); + SetScrollableFrameMetrics(layers[4], FrameMetrics::START_SCROLL_ID + 3, CSSRect(0, 50, 100, 100)); + SetScrollHandoff(layers[2], layers[1]); + SetScrollHandoff(layers[4], layers[3]); + registration = MakeUnique<ScopedLayerTreeRegistration>(manager, 0, root, mcc); + manager->UpdateHitTestingTree(nullptr, root, false, 0, 0); + } + + void CreateScrollgrabLayerTree(bool makeParentScrollable = true) { + const char* layerTreeSyntax = "c(t)"; + nsIntRegion layerVisibleRegion[] = { + nsIntRegion(IntRect(0, 0, 100, 100)), // scroll-grabbing parent + nsIntRegion(IntRect(0, 20, 100, 80)) // child + }; + root = CreateLayerTree(layerTreeSyntax, layerVisibleRegion, nullptr, lm, layers); + float parentHeight = makeParentScrollable ? 120 : 100; + SetScrollableFrameMetrics(root, FrameMetrics::START_SCROLL_ID, CSSRect(0, 0, 100, parentHeight)); + SetScrollableFrameMetrics(layers[1], FrameMetrics::START_SCROLL_ID + 1, CSSRect(0, 0, 100, 200)); + SetScrollHandoff(layers[1], root); + registration = MakeUnique<ScopedLayerTreeRegistration>(manager, 0, root, mcc); + manager->UpdateHitTestingTree(nullptr, root, false, 0, 0); + rootApzc = ApzcOf(root); + rootApzc->GetFrameMetrics().SetHasScrollgrab(true); + } + + void TestFlingAcceleration() { + // Jack up the fling acceleration multiplier so we can easily determine + // whether acceleration occured. + const float kAcceleration = 100.0f; + SCOPED_GFX_PREF(APZFlingAccelBaseMultiplier, float, kAcceleration); + + RefPtr<TestAsyncPanZoomController> childApzc = ApzcOf(layers[1]); + + // Pan once, enough to fully scroll the scrollgrab parent and then scroll + // and fling the child. + Pan(manager, mcc, 70, 40); + + // Give the fling animation a chance to start. + SampleAnimationsOnce(); + + float childVelocityAfterFling1 = childApzc->GetVelocityVector().y; + + // Pan again. + Pan(manager, mcc, 70, 40); + + // Give the fling animation a chance to start. + // This time it should be accelerated. + SampleAnimationsOnce(); + + float childVelocityAfterFling2 = childApzc->GetVelocityVector().y; + + // We should have accelerated once. + // The division by 2 is to account for friction. + EXPECT_GT(childVelocityAfterFling2, + childVelocityAfterFling1 * kAcceleration / 2); + + // We should not have accelerated twice. + // The division by 4 is to account for friction. + EXPECT_LE(childVelocityAfterFling2, + childVelocityAfterFling1 * kAcceleration * kAcceleration / 4); + } +}; + +// Here we test that if the processing of a touch block is deferred while we +// wait for content to send a prevent-default message, overscroll is still +// handed off correctly when the block is processed. +TEST_F(APZOverscrollHandoffTester, DeferredInputEventProcessing) { + // Set up the APZC tree. + CreateOverscrollHandoffLayerTree1(); + + TestAsyncPanZoomController* childApzc = ApzcOf(layers[1]); + + // Enable touch-listeners so that we can separate the queueing of input + // events from them being processed. + childApzc->SetWaitForMainThread(); + + // Queue input events for a pan. + uint64_t blockId = 0; + ApzcPanNoFling(childApzc, mcc, 90, 30, &blockId); + + // Allow the pan to be processed. + childApzc->ContentReceivedInputBlock(blockId, false); + childApzc->ConfirmTarget(blockId); + + // Make sure overscroll was handed off correctly. + EXPECT_EQ(50, childApzc->GetFrameMetrics().GetScrollOffset().y); + EXPECT_EQ(10, rootApzc->GetFrameMetrics().GetScrollOffset().y); +} + +// Here we test that if the layer structure changes in between two input +// blocks being queued, and the first block is only processed after the second +// one has been queued, overscroll handoff for the first block follows +// the original layer structure while overscroll handoff for the second block +// follows the new layer structure. +TEST_F(APZOverscrollHandoffTester, LayerStructureChangesWhileEventsArePending) { + // Set up an initial APZC tree. + CreateOverscrollHandoffLayerTree1(); + + TestAsyncPanZoomController* childApzc = ApzcOf(layers[1]); + + // Enable touch-listeners so that we can separate the queueing of input + // events from them being processed. + childApzc->SetWaitForMainThread(); + + // Queue input events for a pan. + uint64_t blockId = 0; + ApzcPanNoFling(childApzc, mcc, 90, 30, &blockId); + + // Modify the APZC tree to insert a new APZC 'middle' into the handoff chain + // between the child and the root. + CreateOverscrollHandoffLayerTree2(); + RefPtr<Layer> middle = layers[1]; + childApzc->SetWaitForMainThread(); + TestAsyncPanZoomController* middleApzc = ApzcOf(middle); + + // Queue input events for another pan. + uint64_t secondBlockId = 0; + ApzcPanNoFling(childApzc, mcc, 30, 90, &secondBlockId); + + // Allow the first pan to be processed. + childApzc->ContentReceivedInputBlock(blockId, false); + childApzc->ConfirmTarget(blockId); + + // Make sure things have scrolled according to the handoff chain in + // place at the time the touch-start of the first pan was queued. + EXPECT_EQ(50, childApzc->GetFrameMetrics().GetScrollOffset().y); + EXPECT_EQ(10, rootApzc->GetFrameMetrics().GetScrollOffset().y); + EXPECT_EQ(0, middleApzc->GetFrameMetrics().GetScrollOffset().y); + + // Allow the second pan to be processed. + childApzc->ContentReceivedInputBlock(secondBlockId, false); + childApzc->ConfirmTarget(secondBlockId); + + // Make sure things have scrolled according to the handoff chain in + // place at the time the touch-start of the second pan was queued. + EXPECT_EQ(0, childApzc->GetFrameMetrics().GetScrollOffset().y); + EXPECT_EQ(10, rootApzc->GetFrameMetrics().GetScrollOffset().y); + EXPECT_EQ(-10, middleApzc->GetFrameMetrics().GetScrollOffset().y); +} + +// Test that putting a second finger down on an APZC while a down-chain APZC +// is overscrolled doesn't result in being stuck in overscroll. +TEST_F(APZOverscrollHandoffTester, StuckInOverscroll_Bug1073250) { + // Enable overscrolling. + SCOPED_GFX_PREF(APZOverscrollEnabled, bool, true); + + CreateOverscrollHandoffLayerTree1(); + + TestAsyncPanZoomController* child = ApzcOf(layers[1]); + + // Pan, causing the parent APZC to overscroll. + Pan(manager, mcc, 10, 40, true /* keep finger down */); + EXPECT_FALSE(child->IsOverscrolled()); + EXPECT_TRUE(rootApzc->IsOverscrolled()); + + // Put a second finger down. + MultiTouchInput secondFingerDown(MultiTouchInput::MULTITOUCH_START, 0, TimeStamp(), 0); + // Use the same touch identifier for the first touch (0) as Pan(). (A bit hacky.) + secondFingerDown.mTouches.AppendElement(SingleTouchData(0, ScreenIntPoint(10, 40), ScreenSize(0, 0), 0, 0)); + secondFingerDown.mTouches.AppendElement(SingleTouchData(1, ScreenIntPoint(30, 20), ScreenSize(0, 0), 0, 0)); + manager->ReceiveInputEvent(secondFingerDown, nullptr, nullptr); + + // Release the fingers. + MultiTouchInput fingersUp = secondFingerDown; + fingersUp.mType = MultiTouchInput::MULTITOUCH_END; + manager->ReceiveInputEvent(fingersUp, nullptr, nullptr); + + // Allow any animations to run their course. + child->AdvanceAnimationsUntilEnd(); + rootApzc->AdvanceAnimationsUntilEnd(); + + // Make sure nothing is overscrolled. + EXPECT_FALSE(child->IsOverscrolled()); + EXPECT_FALSE(rootApzc->IsOverscrolled()); +} + +// This is almost exactly like StuckInOverscroll_Bug1073250, except the +// APZC receiving the input events for the first touch block is the child +// (and thus not the same APZC that overscrolls, which is the parent). +TEST_F(APZOverscrollHandoffTester, StuckInOverscroll_Bug1231228) { + // Enable overscrolling. + SCOPED_GFX_PREF(APZOverscrollEnabled, bool, true); + + CreateOverscrollHandoffLayerTree1(); + + TestAsyncPanZoomController* child = ApzcOf(layers[1]); + + // Pan, causing the parent APZC to overscroll. + Pan(manager, mcc, 60, 90, true /* keep finger down */); + EXPECT_FALSE(child->IsOverscrolled()); + EXPECT_TRUE(rootApzc->IsOverscrolled()); + + // Put a second finger down. + MultiTouchInput secondFingerDown(MultiTouchInput::MULTITOUCH_START, 0, TimeStamp(), 0); + // Use the same touch identifier for the first touch (0) as Pan(). (A bit hacky.) + secondFingerDown.mTouches.AppendElement(SingleTouchData(0, ScreenIntPoint(10, 40), ScreenSize(0, 0), 0, 0)); + secondFingerDown.mTouches.AppendElement(SingleTouchData(1, ScreenIntPoint(30, 20), ScreenSize(0, 0), 0, 0)); + manager->ReceiveInputEvent(secondFingerDown, nullptr, nullptr); + + // Release the fingers. + MultiTouchInput fingersUp = secondFingerDown; + fingersUp.mType = MultiTouchInput::MULTITOUCH_END; + manager->ReceiveInputEvent(fingersUp, nullptr, nullptr); + + // Allow any animations to run their course. + child->AdvanceAnimationsUntilEnd(); + rootApzc->AdvanceAnimationsUntilEnd(); + + // Make sure nothing is overscrolled. + EXPECT_FALSE(child->IsOverscrolled()); + EXPECT_FALSE(rootApzc->IsOverscrolled()); +} + +// Test that flinging in a direction where one component of the fling goes into +// overscroll but the other doesn't, results in just the one component being +// handed off to the parent, while the original APZC continues flinging in the +// other direction. +TEST_F(APZOverscrollHandoffTester, PartialFlingHandoff) { + CreateOverscrollHandoffLayerTree1(); + + // Fling up and to the left. The child APZC has room to scroll up, but not + // to the left, so the horizontal component of the fling should be handed + // off to the parent APZC. + Pan(manager, mcc, ScreenPoint(90, 90), ScreenPoint(55, 55)); + + RefPtr<TestAsyncPanZoomController> parent = ApzcOf(root); + RefPtr<TestAsyncPanZoomController> child = ApzcOf(layers[1]); + + // Advance the child's fling animation once to give the partial handoff + // a chance to occur. + mcc->AdvanceByMillis(10); + child->AdvanceAnimations(mcc->Time()); + + // Assert that partial handoff has occurred. + child->AssertStateIsFling(); + parent->AssertStateIsFling(); +} + +// Here we test that if two flings are happening simultaneously, overscroll +// is handed off correctly for each. +TEST_F(APZOverscrollHandoffTester, SimultaneousFlings) { + // Set up an initial APZC tree. + CreateOverscrollHandoffLayerTree3(); + + RefPtr<TestAsyncPanZoomController> parent1 = ApzcOf(layers[1]); + RefPtr<TestAsyncPanZoomController> child1 = ApzcOf(layers[2]); + RefPtr<TestAsyncPanZoomController> parent2 = ApzcOf(layers[3]); + RefPtr<TestAsyncPanZoomController> child2 = ApzcOf(layers[4]); + + // Pan on the lower child. + Pan(child2, mcc, 45, 5); + + // Pan on the upper child. + Pan(child1, mcc, 95, 55); + + // Check that child1 and child2 are in a FLING state. + child1->AssertStateIsFling(); + child2->AssertStateIsFling(); + + // Advance the animations on child1 and child2 until their end. + child1->AdvanceAnimationsUntilEnd(); + child2->AdvanceAnimationsUntilEnd(); + + // Check that the flings have been handed off to the parents. + child1->AssertStateIsReset(); + parent1->AssertStateIsFling(); + child2->AssertStateIsReset(); + parent2->AssertStateIsFling(); +} + +TEST_F(APZOverscrollHandoffTester, Scrollgrab) { + // Set up the layer tree + CreateScrollgrabLayerTree(); + + RefPtr<TestAsyncPanZoomController> childApzc = ApzcOf(layers[1]); + + // Pan on the child, enough to fully scroll the scrollgrab parent (20 px) + // and leave some more (another 15 px) for the child. + Pan(childApzc, mcc, 80, 45); + + // Check that the parent and child have scrolled as much as we expect. + EXPECT_EQ(20, rootApzc->GetFrameMetrics().GetScrollOffset().y); + EXPECT_EQ(15, childApzc->GetFrameMetrics().GetScrollOffset().y); +} + +TEST_F(APZOverscrollHandoffTester, ScrollgrabFling) { + // Set up the layer tree + CreateScrollgrabLayerTree(); + + RefPtr<TestAsyncPanZoomController> childApzc = ApzcOf(layers[1]); + + // Pan on the child, not enough to fully scroll the scrollgrab parent. + Pan(childApzc, mcc, 80, 70); + + // Check that it is the scrollgrab parent that's in a fling, not the child. + rootApzc->AssertStateIsFling(); + childApzc->AssertStateIsReset(); +} + +TEST_F(APZOverscrollHandoffTester, ScrollgrabFlingAcceleration1) { + CreateScrollgrabLayerTree(true /* make parent scrollable */); + TestFlingAcceleration(); +} + +TEST_F(APZOverscrollHandoffTester, ScrollgrabFlingAcceleration2) { + CreateScrollgrabLayerTree(false /* do not make parent scrollable */); + TestFlingAcceleration(); +} + +TEST_F(APZOverscrollHandoffTester, ImmediateHandoffDisallowed_Pan) { + SCOPED_GFX_PREF(APZAllowImmediateHandoff, bool, false); + + CreateOverscrollHandoffLayerTree1(); + + RefPtr<TestAsyncPanZoomController> parentApzc = ApzcOf(root); + RefPtr<TestAsyncPanZoomController> childApzc = ApzcOf(layers[1]); + + // Pan on the child, enough to scroll it to its end and have scroll + // left to hand off. Since immediate handoff is disallowed, we expect + // the leftover scroll not to be handed off. + Pan(childApzc, mcc, 60, 5); + + // Verify that the parent has not scrolled. + EXPECT_EQ(50, childApzc->GetFrameMetrics().GetScrollOffset().y); + EXPECT_EQ(0, parentApzc->GetFrameMetrics().GetScrollOffset().y); + + // Pan again on the child. This time, since the child was scrolled to + // its end when the gesture began, we expect the scroll to be handed off. + Pan(childApzc, mcc, 60, 50); + + // Verify that the parent scrolled. + EXPECT_EQ(10, parentApzc->GetFrameMetrics().GetScrollOffset().y); +} + +TEST_F(APZOverscrollHandoffTester, ImmediateHandoffDisallowed_Fling) { + SCOPED_GFX_PREF(APZAllowImmediateHandoff, bool, false); + + CreateOverscrollHandoffLayerTree1(); + + RefPtr<TestAsyncPanZoomController> parentApzc = ApzcOf(root); + RefPtr<TestAsyncPanZoomController> childApzc = ApzcOf(layers[1]); + + // Pan on the child, enough to get very close to the end, so that the + // subsequent fling reaches the end and has leftover velocity to hand off. + Pan(childApzc, mcc, 60, 12); + + // Allow the fling to run its course. + childApzc->AdvanceAnimationsUntilEnd(); + parentApzc->AdvanceAnimationsUntilEnd(); + + // Verify that the parent has not scrolled. + // The first comparison needs to be an ASSERT_NEAR because the fling + // computations are such that the final scroll position can be within + // COORDINATE_EPSILON of the end rather than right at the end. + ASSERT_NEAR(50, childApzc->GetFrameMetrics().GetScrollOffset().y, COORDINATE_EPSILON); + EXPECT_EQ(0, parentApzc->GetFrameMetrics().GetScrollOffset().y); + + // Pan again on the child. This time, since the child was scrolled to + // its end when the gesture began, we expect the scroll to be handed off. + Pan(childApzc, mcc, 60, 50); + + // Allow the fling to run its course. The fling should also be handed off. + childApzc->AdvanceAnimationsUntilEnd(); + parentApzc->AdvanceAnimationsUntilEnd(); + + // Verify that the parent scrolled from the fling. + EXPECT_GT(parentApzc->GetFrameMetrics().GetScrollOffset().y, 10); +}
new file mode 100644 --- /dev/null +++ b/gfx/layers/apz/test/gtest/TestPanning.cpp @@ -0,0 +1,124 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set sw=2 ts=8 et 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 "APZCBasicTester.h" +#include "APZTestCommon.h" +#include "InputUtils.h" + +class APZCPanningTester : public APZCBasicTester { +protected: + void DoPanTest(bool aShouldTriggerScroll, bool aShouldBeConsumed, uint32_t aBehavior) + { + if (aShouldTriggerScroll) { + // One repaint request for each pan. + EXPECT_CALL(*mcc, RequestContentRepaint(_)).Times(2); + } else { + EXPECT_CALL(*mcc, RequestContentRepaint(_)).Times(0); + } + + int touchStart = 50; + int touchEnd = 10; + ParentLayerPoint pointOut; + AsyncTransform viewTransformOut; + + nsTArray<uint32_t> allowedTouchBehaviors; + allowedTouchBehaviors.AppendElement(aBehavior); + + // Pan down + PanAndCheckStatus(apzc, mcc, touchStart, touchEnd, aShouldBeConsumed, &allowedTouchBehaviors); + apzc->SampleContentTransformForFrame(&viewTransformOut, pointOut); + + if (aShouldTriggerScroll) { + EXPECT_EQ(ParentLayerPoint(0, -(touchEnd-touchStart)), pointOut); + EXPECT_NE(AsyncTransform(), viewTransformOut); + } else { + EXPECT_EQ(ParentLayerPoint(), pointOut); + EXPECT_EQ(AsyncTransform(), viewTransformOut); + } + + // Clear the fling from the previous pan, or stopping it will + // consume the next touchstart + apzc->CancelAnimation(); + + // Pan back + PanAndCheckStatus(apzc, mcc, touchEnd, touchStart, aShouldBeConsumed, &allowedTouchBehaviors); + apzc->SampleContentTransformForFrame(&viewTransformOut, pointOut); + + EXPECT_EQ(ParentLayerPoint(), pointOut); + EXPECT_EQ(AsyncTransform(), viewTransformOut); + } + + void DoPanWithPreventDefaultTest() + { + MakeApzcWaitForMainThread(); + + int touchStart = 50; + int touchEnd = 10; + ParentLayerPoint pointOut; + AsyncTransform viewTransformOut; + uint64_t blockId = 0; + + // Pan down + nsTArray<uint32_t> allowedTouchBehaviors; + allowedTouchBehaviors.AppendElement(mozilla::layers::AllowedTouchBehavior::VERTICAL_PAN); + PanAndCheckStatus(apzc, mcc, touchStart, touchEnd, true, &allowedTouchBehaviors, &blockId); + + // Send the signal that content has handled and preventDefaulted the touch + // events. This flushes the event queue. + apzc->ContentReceivedInputBlock(blockId, true); + + apzc->SampleContentTransformForFrame(&viewTransformOut, pointOut); + EXPECT_EQ(ParentLayerPoint(), pointOut); + EXPECT_EQ(AsyncTransform(), viewTransformOut); + + apzc->AssertStateIsReset(); + } +}; + +TEST_F(APZCPanningTester, Pan) { + SCOPED_GFX_PREF(TouchActionEnabled, bool, false); + DoPanTest(true, true, mozilla::layers::AllowedTouchBehavior::NONE); +} + +// In the each of the following 4 pan tests we are performing two pan gestures: vertical pan from top +// to bottom and back - from bottom to top. +// According to the pointer-events/touch-action spec AUTO and PAN_Y touch-action values allow vertical +// scrolling while NONE and PAN_X forbid it. The first parameter of DoPanTest method specifies this +// behavior. +// However, the events will be marked as consumed even if the behavior in PAN_X, because the user could +// move their finger horizontally too - APZ has no way of knowing beforehand and so must consume the +// events. +TEST_F(APZCPanningTester, PanWithTouchActionAuto) { + SCOPED_GFX_PREF(TouchActionEnabled, bool, true); + DoPanTest(true, true, mozilla::layers::AllowedTouchBehavior::HORIZONTAL_PAN + | mozilla::layers::AllowedTouchBehavior::VERTICAL_PAN); +} + +TEST_F(APZCPanningTester, PanWithTouchActionNone) { + SCOPED_GFX_PREF(TouchActionEnabled, bool, true); + DoPanTest(false, false, 0); +} + +TEST_F(APZCPanningTester, PanWithTouchActionPanX) { + SCOPED_GFX_PREF(TouchActionEnabled, bool, true); + DoPanTest(false, true, mozilla::layers::AllowedTouchBehavior::HORIZONTAL_PAN); +} + +TEST_F(APZCPanningTester, PanWithTouchActionPanY) { + SCOPED_GFX_PREF(TouchActionEnabled, bool, true); + DoPanTest(true, true, mozilla::layers::AllowedTouchBehavior::VERTICAL_PAN); +} + +TEST_F(APZCPanningTester, PanWithPreventDefaultAndTouchAction) { + SCOPED_GFX_PREF(TouchActionEnabled, bool, true); + DoPanWithPreventDefaultTest(); +} + +TEST_F(APZCPanningTester, PanWithPreventDefault) { + SCOPED_GFX_PREF(TouchActionEnabled, bool, false); + DoPanWithPreventDefaultTest(); +} +
new file mode 100644 --- /dev/null +++ b/gfx/layers/apz/test/gtest/TestPinching.cpp @@ -0,0 +1,175 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set sw=2 ts=8 et 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 "APZCBasicTester.h" +#include "APZTestCommon.h" +#include "InputUtils.h" + +class APZCPinchTester : public APZCBasicTester { +public: + explicit APZCPinchTester(AsyncPanZoomController::GestureBehavior aGestureBehavior = AsyncPanZoomController::DEFAULT_GESTURES) + : APZCBasicTester(aGestureBehavior) + { + } + +protected: + FrameMetrics GetPinchableFrameMetrics() + { + FrameMetrics fm; + fm.SetCompositionBounds(ParentLayerRect(200, 200, 100, 200)); + fm.SetScrollableRect(CSSRect(0, 0, 980, 1000)); + fm.SetScrollOffset(CSSPoint(300, 300)); + fm.SetZoom(CSSToParentLayerScale2D(2.0, 2.0)); + // APZC only allows zooming on the root scrollable frame. + fm.SetIsRootContent(true); + // the visible area of the document in CSS pixels is x=300 y=300 w=50 h=100 + return fm; + } + + void DoPinchTest(bool aShouldTriggerPinch, + nsTArray<uint32_t> *aAllowedTouchBehaviors = nullptr) + { + apzc->SetFrameMetrics(GetPinchableFrameMetrics()); + MakeApzcZoomable(); + + if (aShouldTriggerPinch) { + // One repaint request for each gesture. + EXPECT_CALL(*mcc, RequestContentRepaint(_)).Times(2); + } else { + EXPECT_CALL(*mcc, RequestContentRepaint(_)).Times(0); + } + + int touchInputId = 0; + if (mGestureBehavior == AsyncPanZoomController::USE_GESTURE_DETECTOR) { + PinchWithTouchInputAndCheckStatus(apzc, 250, 300, 1.25, touchInputId, aShouldTriggerPinch, aAllowedTouchBehaviors); + } else { + PinchWithPinchInputAndCheckStatus(apzc, 250, 300, 1.25, aShouldTriggerPinch); + } + + FrameMetrics fm = apzc->GetFrameMetrics(); + + if (aShouldTriggerPinch) { + // the visible area of the document in CSS pixels is now x=305 y=310 w=40 h=80 + EXPECT_EQ(2.5f, fm.GetZoom().ToScaleFactor().scale); + EXPECT_EQ(305, fm.GetScrollOffset().x); + EXPECT_EQ(310, fm.GetScrollOffset().y); + } else { + // The frame metrics should stay the same since touch-action:none makes + // apzc ignore pinch gestures. + EXPECT_EQ(2.0f, fm.GetZoom().ToScaleFactor().scale); + EXPECT_EQ(300, fm.GetScrollOffset().x); + EXPECT_EQ(300, fm.GetScrollOffset().y); + } + + // part 2 of the test, move to the top-right corner of the page and pinch and + // make sure we stay in the correct spot + fm.SetZoom(CSSToParentLayerScale2D(2.0, 2.0)); + fm.SetScrollOffset(CSSPoint(930, 5)); + apzc->SetFrameMetrics(fm); + // the visible area of the document in CSS pixels is x=930 y=5 w=50 h=100 + + if (mGestureBehavior == AsyncPanZoomController::USE_GESTURE_DETECTOR) { + PinchWithTouchInputAndCheckStatus(apzc, 250, 300, 0.5, touchInputId, aShouldTriggerPinch, aAllowedTouchBehaviors); + } else { + PinchWithPinchInputAndCheckStatus(apzc, 250, 300, 0.5, aShouldTriggerPinch); + } + + fm = apzc->GetFrameMetrics(); + + if (aShouldTriggerPinch) { + // the visible area of the document in CSS pixels is now x=880 y=0 w=100 h=200 + EXPECT_EQ(1.0f, fm.GetZoom().ToScaleFactor().scale); + EXPECT_EQ(880, fm.GetScrollOffset().x); + EXPECT_EQ(0, fm.GetScrollOffset().y); + } else { + EXPECT_EQ(2.0f, fm.GetZoom().ToScaleFactor().scale); + EXPECT_EQ(930, fm.GetScrollOffset().x); + EXPECT_EQ(5, fm.GetScrollOffset().y); + } + } +}; + +class APZCPinchGestureDetectorTester : public APZCPinchTester { +public: + APZCPinchGestureDetectorTester() + : APZCPinchTester(AsyncPanZoomController::USE_GESTURE_DETECTOR) + { + } +}; + +TEST_F(APZCPinchTester, Pinch_DefaultGestures_NoTouchAction) { + SCOPED_GFX_PREF(TouchActionEnabled, bool, false); + DoPinchTest(true); +} + +TEST_F(APZCPinchGestureDetectorTester, Pinch_UseGestureDetector_NoTouchAction) { + SCOPED_GFX_PREF(TouchActionEnabled, bool, false); + DoPinchTest(true); +} + +TEST_F(APZCPinchGestureDetectorTester, Pinch_UseGestureDetector_TouchActionNone) { + SCOPED_GFX_PREF(TouchActionEnabled, bool, true); + nsTArray<uint32_t> behaviors = { mozilla::layers::AllowedTouchBehavior::NONE, + mozilla::layers::AllowedTouchBehavior::NONE }; + DoPinchTest(false, &behaviors); +} + +TEST_F(APZCPinchGestureDetectorTester, Pinch_UseGestureDetector_TouchActionZoom) { + SCOPED_GFX_PREF(TouchActionEnabled, bool, true); + nsTArray<uint32_t> behaviors; + behaviors.AppendElement(mozilla::layers::AllowedTouchBehavior::PINCH_ZOOM); + behaviors.AppendElement(mozilla::layers::AllowedTouchBehavior::PINCH_ZOOM); + DoPinchTest(true, &behaviors); +} + +TEST_F(APZCPinchGestureDetectorTester, Pinch_UseGestureDetector_TouchActionNotAllowZoom) { + SCOPED_GFX_PREF(TouchActionEnabled, bool, true); + nsTArray<uint32_t> behaviors; + behaviors.AppendElement(mozilla::layers::AllowedTouchBehavior::VERTICAL_PAN); + behaviors.AppendElement(mozilla::layers::AllowedTouchBehavior::PINCH_ZOOM); + DoPinchTest(false, &behaviors); +} + +TEST_F(APZCPinchGestureDetectorTester, Pinch_PreventDefault) { + FrameMetrics originalMetrics = GetPinchableFrameMetrics(); + apzc->SetFrameMetrics(originalMetrics); + + MakeApzcWaitForMainThread(); + MakeApzcZoomable(); + + int touchInputId = 0; + uint64_t blockId = 0; + PinchWithTouchInput(apzc, 250, 300, 1.25, touchInputId, nullptr, nullptr, &blockId); + + // Send the prevent-default notification for the touch block + apzc->ContentReceivedInputBlock(blockId, true); + + // verify the metrics didn't change (i.e. the pinch was ignored) + FrameMetrics fm = apzc->GetFrameMetrics(); + EXPECT_EQ(originalMetrics.GetZoom(), fm.GetZoom()); + EXPECT_EQ(originalMetrics.GetScrollOffset().x, fm.GetScrollOffset().x); + EXPECT_EQ(originalMetrics.GetScrollOffset().y, fm.GetScrollOffset().y); + + apzc->AssertStateIsReset(); +} + +TEST_F(APZCPinchTester, Panning_TwoFinger_ZoomDisabled) { + // set up APZ + apzc->SetFrameMetrics(GetPinchableFrameMetrics()); + MakeApzcUnzoomable(); + + nsEventStatus statuses[3]; // scalebegin, scale, scaleend + PinchWithPinchInput(apzc, 250, 350, 200, 300, 10, &statuses); + + FrameMetrics fm = apzc->GetFrameMetrics(); + + // It starts from (300, 300), then moves the focus point from (250, 350) to + // (200, 300) pans by (50, 50) screen pixels, but there is a 2x zoom, which + // causes the scroll offset to change by half of that (25, 25) pixels. + EXPECT_EQ(325, fm.GetScrollOffset().x); + EXPECT_EQ(325, fm.GetScrollOffset().y); + EXPECT_EQ(2.0, fm.GetZoom().ToScaleFactor().scale); +}
new file mode 100644 --- /dev/null +++ b/gfx/layers/apz/test/gtest/TestTreeManager.cpp @@ -0,0 +1,112 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set sw=2 ts=8 et 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 "APZCTreeManagerTester.h" +#include "APZTestCommon.h" +#include "InputUtils.h" + +TEST_F(APZCTreeManagerTester, ScrollablePaintedLayers) { + CreateSimpleMultiLayerTree(); + ScopedLayerTreeRegistration registration(manager, 0, root, mcc); + + // both layers have the same scrollId + SetScrollableFrameMetrics(layers[1], FrameMetrics::START_SCROLL_ID); + SetScrollableFrameMetrics(layers[2], FrameMetrics::START_SCROLL_ID); + manager->UpdateHitTestingTree(nullptr, root, false, 0, 0); + + TestAsyncPanZoomController* nullAPZC = nullptr; + // so they should have the same APZC + EXPECT_FALSE(layers[0]->HasScrollableFrameMetrics()); + EXPECT_NE(nullAPZC, ApzcOf(layers[1])); + EXPECT_NE(nullAPZC, ApzcOf(layers[2])); + EXPECT_EQ(ApzcOf(layers[1]), ApzcOf(layers[2])); + + // Change the scrollId of layers[1], and verify the APZC changes + SetScrollableFrameMetrics(layers[1], FrameMetrics::START_SCROLL_ID + 1); + manager->UpdateHitTestingTree(nullptr, root, false, 0, 0); + EXPECT_NE(ApzcOf(layers[1]), ApzcOf(layers[2])); + + // Change the scrollId of layers[2] to match that of layers[1], ensure we get the same + // APZC for both again + SetScrollableFrameMetrics(layers[2], FrameMetrics::START_SCROLL_ID + 1); + manager->UpdateHitTestingTree(nullptr, root, false, 0, 0); + EXPECT_EQ(ApzcOf(layers[1]), ApzcOf(layers[2])); +} +